buffer_spec.lua (83745B)
1 local t = require('test.testutil') 2 local n = require('test.functional.testnvim')() 3 local Screen = require('test.functional.ui.screen') 4 5 local clear = n.clear 6 local eq = t.eq 7 local ok = t.ok 8 local describe_lua_and_rpc = n.describe_lua_and_rpc(describe) 9 local api = n.api 10 local fn = n.fn 11 local request = n.request 12 local exec_lua = n.exec_lua 13 local insert = n.insert 14 local NIL = vim.NIL 15 local command = n.command 16 local feed = n.feed 17 local pcall_err = t.pcall_err 18 local assert_alive = n.assert_alive 19 20 describe('api/buf', function() 21 before_each(clear) 22 23 -- access deprecated functions 24 local function curbuf_depr(method, ...) 25 return request('buffer_' .. method, 0, ...) 26 end 27 28 describe('nvim_buf_set_lines, nvim_buf_line_count', function() 29 it('deprecated forms', function() 30 eq(1, curbuf_depr('line_count')) 31 curbuf_depr('insert', -1, { 'line' }) 32 eq(2, curbuf_depr('line_count')) 33 curbuf_depr('insert', -1, { 'line' }) 34 eq(3, curbuf_depr('line_count')) 35 curbuf_depr('del_line', -1) 36 eq(2, curbuf_depr('line_count')) 37 curbuf_depr('del_line', -1) 38 curbuf_depr('del_line', -1) 39 -- There's always at least one line 40 eq(1, curbuf_depr('line_count')) 41 end) 42 43 it("doesn't crash just after set undolevels=1 #24894", function() 44 local buf = api.nvim_create_buf(false, true) 45 api.nvim_set_option_value('undolevels', -1, { buf = buf }) 46 api.nvim_buf_set_lines(buf, 0, 1, false, {}) 47 48 assert_alive() 49 end) 50 51 it('cursor position is maintained after lines are inserted #9961', function() 52 -- replace the buffer contents with these three lines. 53 api.nvim_buf_set_lines(0, 0, -1, true, { 'line1', 'line2', 'line3', 'line4' }) 54 -- Set the current cursor to {3, 2}. 55 api.nvim_win_set_cursor(0, { 3, 2 }) 56 57 -- add 2 lines and delete 1 line above the current cursor position. 58 api.nvim_buf_set_lines(0, 1, 2, true, { 'line5', 'line6' }) 59 -- check the current set of lines in the buffer. 60 eq({ 'line1', 'line5', 'line6', 'line3', 'line4' }, api.nvim_buf_get_lines(0, 0, -1, true)) 61 -- cursor should be moved below by 1 line. 62 eq({ 4, 2 }, api.nvim_win_get_cursor(0)) 63 64 -- add a line after the current cursor position. 65 api.nvim_buf_set_lines(0, 5, 5, true, { 'line7' }) 66 -- check the current set of lines in the buffer. 67 eq( 68 { 'line1', 'line5', 'line6', 'line3', 'line4', 'line7' }, 69 api.nvim_buf_get_lines(0, 0, -1, true) 70 ) 71 -- cursor position is unchanged. 72 eq({ 4, 2 }, api.nvim_win_get_cursor(0)) 73 74 -- overwrite current cursor line. 75 api.nvim_buf_set_lines(0, 3, 5, true, { 'line8', 'line9' }) 76 -- check the current set of lines in the buffer. 77 eq( 78 { 'line1', 'line5', 'line6', 'line8', 'line9', 'line7' }, 79 api.nvim_buf_get_lines(0, 0, -1, true) 80 ) 81 -- cursor position is unchanged. 82 eq({ 4, 2 }, api.nvim_win_get_cursor(0)) 83 84 -- delete current cursor line. 85 api.nvim_buf_set_lines(0, 3, 5, true, {}) 86 -- check the current set of lines in the buffer. 87 eq({ 'line1', 'line5', 'line6', 'line7' }, api.nvim_buf_get_lines(0, 0, -1, true)) 88 -- cursor position is unchanged. 89 eq({ 4, 2 }, api.nvim_win_get_cursor(0)) 90 end) 91 92 it('cursor position is maintained in non-current window', function() 93 api.nvim_buf_set_lines(0, 0, -1, true, { 'line1', 'line2', 'line3', 'line4' }) 94 api.nvim_win_set_cursor(0, { 3, 2 }) 95 local win = api.nvim_get_current_win() 96 local buf = api.nvim_get_current_buf() 97 98 command('new') 99 100 api.nvim_buf_set_lines(buf, 1, 2, true, { 'line5', 'line6' }) 101 eq({ 'line1', 'line5', 'line6', 'line3', 'line4' }, api.nvim_buf_get_lines(buf, 0, -1, true)) 102 eq({ 4, 2 }, api.nvim_win_get_cursor(win)) 103 end) 104 105 it('cursor position is maintained in TWO non-current windows', function() 106 api.nvim_buf_set_lines(0, 0, -1, true, { 'line1', 'line2', 'line3', 'line4' }) 107 api.nvim_win_set_cursor(0, { 3, 2 }) 108 local win = api.nvim_get_current_win() 109 local buf = api.nvim_get_current_buf() 110 111 command('split') 112 api.nvim_win_set_cursor(0, { 4, 2 }) 113 local win2 = api.nvim_get_current_win() 114 115 -- set current window to third one with another buffer 116 command('new') 117 118 api.nvim_buf_set_lines(buf, 1, 2, true, { 'line5', 'line6' }) 119 eq({ 'line1', 'line5', 'line6', 'line3', 'line4' }, api.nvim_buf_get_lines(buf, 0, -1, true)) 120 eq({ 4, 2 }, api.nvim_win_get_cursor(win)) 121 eq({ 5, 2 }, api.nvim_win_get_cursor(win2)) 122 end) 123 124 it('cursor position is maintained consistently with viewport', function() 125 local screen = Screen.new(20, 12) 126 127 local lines = { 'line1', 'line2', 'line3', 'line4', 'line5', 'line6' } 128 local buf = api.nvim_get_current_buf() 129 130 api.nvim_buf_set_lines(buf, 0, -1, true, lines) 131 132 command('6') 133 command('new') 134 screen:expect { 135 grid = [[ 136 ^ | 137 {1:~ }|*4 138 {3:[No Name] }| 139 line5 | 140 line6 | 141 {1:~ }|*2 142 {2:[No Name] [+] }| 143 | 144 ]], 145 } 146 147 lines[5] = 'boogalo 5' 148 api.nvim_buf_set_lines(buf, 0, -1, true, lines) 149 screen:expect { 150 grid = [[ 151 ^ | 152 {1:~ }|*4 153 {3:[No Name] }| 154 boogalo 5 | 155 line6 | 156 {1:~ }|*2 157 {2:[No Name] [+] }| 158 | 159 ]], 160 } 161 162 command('wincmd w') 163 screen:expect { 164 grid = [[ 165 | 166 {1:~ }|*4 167 {2:[No Name] }| 168 boogalo 5 | 169 ^line6 | 170 {1:~ }|*2 171 {3:[No Name] [+] }| 172 | 173 ]], 174 } 175 end) 176 177 it('line_count has defined behaviour for unloaded buffers', function() 178 -- we'll need to know our bufnr for when it gets unloaded 179 local bufnr = api.nvim_get_current_buf() 180 -- replace the buffer contents with these three lines 181 api.nvim_buf_set_lines(bufnr, 0, -1, true, { 'line1', 'line2', 'line3', 'line4' }) 182 -- check the line count is correct 183 eq(4, api.nvim_buf_line_count(bufnr)) 184 -- force unload the buffer (this will discard changes) 185 command('new') 186 command('bunload! ' .. bufnr) 187 -- line count for an unloaded buffer should always be 0 188 eq(0, api.nvim_buf_line_count(bufnr)) 189 end) 190 191 it('get_lines has defined behaviour for unloaded buffers', function() 192 -- we'll need to know our bufnr for when it gets unloaded 193 local bufnr = api.nvim_get_current_buf() 194 -- replace the buffer contents with these three lines 195 api.nvim_buf_set_lines(bufnr, 0, -1, true, { 'line1', 'line2', 'line3', 'line4' }) 196 -- confirm that getting lines works 197 eq({ 'line2', 'line3' }, api.nvim_buf_get_lines(bufnr, 1, 3, true)) 198 -- force unload the buffer (this will discard changes) 199 command('new') 200 command('bunload! ' .. bufnr) 201 -- attempting to get lines now always gives empty list 202 eq({}, api.nvim_buf_get_lines(bufnr, 1, 3, true)) 203 -- it's impossible to get out-of-bounds errors for an unloaded buffer 204 eq({}, api.nvim_buf_get_lines(bufnr, 8888, 9999, true)) 205 end) 206 207 describe('handles topline', function() 208 local screen 209 before_each(function() 210 screen = Screen.new(20, 12) 211 api.nvim_buf_set_lines( 212 0, 213 0, 214 -1, 215 true, 216 { 'aaa', 'bbb', 'ccc', 'ddd', 'www', 'xxx', 'yyy', 'zzz' } 217 ) 218 api.nvim_set_option_value('modified', false, {}) 219 end) 220 221 it('of current window', function() 222 local win = api.nvim_get_current_win() 223 local buf = api.nvim_get_current_buf() 224 225 command('new | wincmd w') 226 api.nvim_win_set_cursor(win, { 8, 0 }) 227 228 screen:expect { 229 grid = [[ 230 | 231 {1:~ }|*4 232 {2:[No Name] }| 233 www | 234 xxx | 235 yyy | 236 ^zzz | 237 {3:[No Name] }| 238 | 239 ]], 240 } 241 242 api.nvim_buf_set_lines(buf, 0, 2, true, { 'aaabbb' }) 243 screen:expect { 244 grid = [[ 245 | 246 {1:~ }|*4 247 {2:[No Name] }| 248 www | 249 xxx | 250 yyy | 251 ^zzz | 252 {3:[No Name] [+] }| 253 | 254 ]], 255 } 256 257 -- replacing topline keeps it the topline 258 api.nvim_buf_set_lines(buf, 3, 4, true, { 'wwweeee' }) 259 screen:expect { 260 grid = [[ 261 | 262 {1:~ }|*4 263 {2:[No Name] }| 264 wwweeee | 265 xxx | 266 yyy | 267 ^zzz | 268 {3:[No Name] [+] }| 269 | 270 ]], 271 } 272 273 -- inserting just before topline does not scroll up if cursor would be moved 274 api.nvim_buf_set_lines(buf, 3, 3, true, { 'mmm' }) 275 screen:expect { 276 grid = [[ 277 | 278 {1:~ }|*4 279 {2:[No Name] }| 280 wwweeee | 281 xxx | 282 yyy | 283 ^zzz | 284 {3:[No Name] [+] }| 285 | 286 ]], 287 unchanged = true, 288 } 289 290 api.nvim_win_set_cursor(0, { 7, 0 }) 291 screen:expect { 292 grid = [[ 293 | 294 {1:~ }|*4 295 {2:[No Name] }| 296 wwweeee | 297 xxx | 298 ^yyy | 299 zzz | 300 {3:[No Name] [+] }| 301 | 302 ]], 303 } 304 305 api.nvim_buf_set_lines(buf, 4, 4, true, { 'mmmeeeee' }) 306 screen:expect { 307 grid = [[ 308 | 309 {1:~ }|*4 310 {2:[No Name] }| 311 mmmeeeee | 312 wwweeee | 313 xxx | 314 ^yyy | 315 {3:[No Name] [+] }| 316 | 317 ]], 318 } 319 end) 320 321 it('of non-current window', function() 322 local win = api.nvim_get_current_win() 323 local buf = api.nvim_get_current_buf() 324 325 command('new') 326 api.nvim_win_set_cursor(win, { 8, 0 }) 327 328 screen:expect { 329 grid = [[ 330 ^ | 331 {1:~ }|*4 332 {3:[No Name] }| 333 www | 334 xxx | 335 yyy | 336 zzz | 337 {2:[No Name] }| 338 | 339 ]], 340 } 341 342 api.nvim_buf_set_lines(buf, 0, 2, true, { 'aaabbb' }) 343 screen:expect { 344 grid = [[ 345 ^ | 346 {1:~ }|*4 347 {3:[No Name] }| 348 www | 349 xxx | 350 yyy | 351 zzz | 352 {2:[No Name] [+] }| 353 | 354 ]], 355 } 356 357 -- replacing topline keeps it the topline 358 api.nvim_buf_set_lines(buf, 3, 4, true, { 'wwweeee' }) 359 screen:expect { 360 grid = [[ 361 ^ | 362 {1:~ }|*4 363 {3:[No Name] }| 364 wwweeee | 365 xxx | 366 yyy | 367 zzz | 368 {2:[No Name] [+] }| 369 | 370 ]], 371 } 372 373 api.nvim_buf_set_lines(buf, 3, 3, true, { 'mmm' }) 374 screen:expect { 375 grid = [[ 376 ^ | 377 {1:~ }|*4 378 {3:[No Name] }| 379 wwweeee | 380 xxx | 381 yyy | 382 zzz | 383 {2:[No Name] [+] }| 384 | 385 ]], 386 unchanged = true, 387 } 388 end) 389 390 it('of split windows with same buffer', function() 391 local win = api.nvim_get_current_win() 392 local buf = api.nvim_get_current_buf() 393 394 command('split') 395 api.nvim_win_set_cursor(win, { 8, 0 }) 396 api.nvim_win_set_cursor(0, { 1, 0 }) 397 398 screen:expect { 399 grid = [[ 400 ^aaa | 401 bbb | 402 ccc | 403 ddd | 404 www | 405 {3:[No Name] }| 406 www | 407 xxx | 408 yyy | 409 zzz | 410 {2:[No Name] }| 411 | 412 ]], 413 } 414 api.nvim_buf_set_lines(buf, 0, 2, true, { 'aaabbb' }) 415 416 screen:expect { 417 grid = [[ 418 ^aaabbb | 419 ccc | 420 ddd | 421 www | 422 xxx | 423 {3:[No Name] [+] }| 424 www | 425 xxx | 426 yyy | 427 zzz | 428 {2:[No Name] [+] }| 429 | 430 ]], 431 } 432 433 -- replacing topline keeps it the topline 434 api.nvim_buf_set_lines(buf, 3, 4, true, { 'wwweeee' }) 435 screen:expect { 436 grid = [[ 437 ^aaabbb | 438 ccc | 439 ddd | 440 wwweeee | 441 xxx | 442 {3:[No Name] [+] }| 443 wwweeee | 444 xxx | 445 yyy | 446 zzz | 447 {2:[No Name] [+] }| 448 | 449 ]], 450 } 451 452 api.nvim_buf_set_lines(buf, 3, 3, true, { 'mmm' }) 453 screen:expect { 454 grid = [[ 455 ^aaabbb | 456 ccc | 457 ddd | 458 mmm | 459 wwweeee | 460 {3:[No Name] [+] }| 461 wwweeee | 462 xxx | 463 yyy | 464 zzz | 465 {2:[No Name] [+] }| 466 | 467 ]], 468 } 469 end) 470 471 describe('of current window when', function() 472 before_each(function() 473 command('new | wincmd w | setlocal modified') 474 feed('Gk') 475 screen:expect([[ 476 | 477 {1:~ }|*4 478 {2:[No Name] }| 479 www | 480 xxx | 481 ^yyy | 482 zzz | 483 {3:[No Name] [+] }| 484 | 485 ]]) 486 end) 487 488 it('deleting 3 lines around topline', function() 489 api.nvim_buf_set_lines(0, 3, 6, true, {}) 490 screen:expect([[ 491 | 492 {1:~ }|*4 493 {2:[No Name] }| 494 ccc | 495 ^yyy | 496 zzz | 497 {1:~ }| 498 {3:[No Name] [+] }| 499 | 500 ]]) 501 eq(5, api.nvim_buf_line_count(0)) 502 end) 503 504 it('deleting 3 lines around the line just before topline', function() 505 api.nvim_buf_set_lines(0, 2, 5, true, {}) 506 screen:expect([[ 507 | 508 {1:~ }|*4 509 {2:[No Name] }| 510 bbb | 511 xxx | 512 ^yyy | 513 zzz | 514 {3:[No Name] [+] }| 515 | 516 ]]) 517 eq(5, api.nvim_buf_line_count(0)) 518 end) 519 520 for count = 1, 4 do 521 it(('deleting %d lines just before topline'):format(count), function() 522 api.nvim_buf_set_lines(0, 4 - count, 4, true, {}) 523 screen:expect_unchanged() 524 eq(8 - count, api.nvim_buf_line_count(0)) 525 end) 526 527 it(('replacing %d lines just before topline with 2 lines'):format(count), function() 528 api.nvim_buf_set_lines(0, 4 - count, 4, true, { 'eee', 'fff' }) 529 screen:expect_unchanged() 530 eq(8 - count + 2, api.nvim_buf_line_count(0)) 531 end) 532 end 533 534 for count = 1, 3 do 535 it(('deleting %d lines far before topline'):format(count), function() 536 api.nvim_buf_set_lines(0, 0, count, true, {}) 537 screen:expect_unchanged() 538 eq(8 - count, api.nvim_buf_line_count(0)) 539 end) 540 541 it(('replacing %d lines far before topline with 2 lines'):format(count), function() 542 api.nvim_buf_set_lines(0, 0, count, true, { 'eee', 'fff' }) 543 screen:expect_unchanged() 544 eq(8 - count + 2, api.nvim_buf_line_count(0)) 545 end) 546 end 547 end) 548 end) 549 550 it('handles clearing out non-current buffer #24911', function() 551 local buf = api.nvim_get_current_buf() 552 api.nvim_buf_set_lines(buf, 0, -1, true, { 'aaa', 'bbb', 'ccc' }) 553 command('new') 554 555 api.nvim_buf_set_lines(0, 0, -1, true, { 'xxx', 'yyy', 'zzz' }) 556 557 api.nvim_buf_set_lines(buf, 0, -1, true, {}) 558 eq({ 'xxx', 'yyy', 'zzz' }, api.nvim_buf_get_lines(0, 0, -1, true)) 559 eq({ '' }, api.nvim_buf_get_lines(buf, 0, -1, true)) 560 end) 561 end) 562 563 describe('deprecated: {get,set,del}_line', function() 564 it('works', function() 565 eq('', curbuf_depr('get_line', 0)) 566 curbuf_depr('set_line', 0, 'line1') 567 eq('line1', curbuf_depr('get_line', 0)) 568 curbuf_depr('set_line', 0, 'line2') 569 eq('line2', curbuf_depr('get_line', 0)) 570 curbuf_depr('del_line', 0) 571 eq('', curbuf_depr('get_line', 0)) 572 end) 573 574 it('get_line: out-of-bounds is an error', function() 575 curbuf_depr('set_line', 0, 'line1.a') 576 eq(1, curbuf_depr('line_count')) -- sanity 577 eq(false, pcall(curbuf_depr, 'get_line', 1)) 578 eq(false, pcall(curbuf_depr, 'get_line', -2)) 579 end) 580 581 it('set_line, del_line: out-of-bounds is an error', function() 582 curbuf_depr('set_line', 0, 'line1.a') 583 eq(false, pcall(curbuf_depr, 'set_line', 1, 'line1.b')) 584 eq(false, pcall(curbuf_depr, 'set_line', -2, 'line1.b')) 585 eq(false, pcall(curbuf_depr, 'del_line', 2)) 586 eq(false, pcall(curbuf_depr, 'del_line', -3)) 587 end) 588 589 it('can handle NULs', function() 590 curbuf_depr('set_line', 0, 'ab\0cd') 591 eq('ab\0cd', curbuf_depr('get_line', 0)) 592 end) 593 end) 594 595 describe('deprecated: {get,set}_line_slice', function() 596 it('get_line_slice: out-of-bounds returns empty array', function() 597 curbuf_depr('set_line_slice', 0, 0, true, true, { 'a', 'b', 'c' }) 598 eq({ 'a', 'b', 'c' }, curbuf_depr('get_line_slice', 0, 2, true, true)) --sanity 599 600 eq({}, curbuf_depr('get_line_slice', 2, 3, false, true)) 601 eq({}, curbuf_depr('get_line_slice', 3, 9, true, true)) 602 eq({}, curbuf_depr('get_line_slice', 3, -1, true, true)) 603 eq({}, curbuf_depr('get_line_slice', -3, -4, false, true)) 604 eq({}, curbuf_depr('get_line_slice', -4, -5, true, true)) 605 end) 606 607 it('set_line_slice: out-of-bounds extends past end', function() 608 curbuf_depr('set_line_slice', 0, 0, true, true, { 'a', 'b', 'c' }) 609 eq({ 'a', 'b', 'c' }, curbuf_depr('get_line_slice', 0, 2, true, true)) --sanity 610 611 eq({ 'c' }, curbuf_depr('get_line_slice', -1, 4, true, true)) 612 eq({ 'a', 'b', 'c' }, curbuf_depr('get_line_slice', 0, 5, true, true)) 613 curbuf_depr('set_line_slice', 4, 5, true, true, { 'd' }) 614 eq({ 'a', 'b', 'c', 'd' }, curbuf_depr('get_line_slice', 0, 5, true, true)) 615 curbuf_depr('set_line_slice', -4, -5, true, true, { 'e' }) 616 eq({ 'e', 'a', 'b', 'c', 'd' }, curbuf_depr('get_line_slice', 0, 5, true, true)) 617 end) 618 619 it('works', function() 620 eq({ '' }, curbuf_depr('get_line_slice', 0, -1, true, true)) 621 -- Replace buffer 622 curbuf_depr('set_line_slice', 0, -1, true, true, { 'a', 'b', 'c' }) 623 eq({ 'a', 'b', 'c' }, curbuf_depr('get_line_slice', 0, -1, true, true)) 624 eq({ 'b', 'c' }, curbuf_depr('get_line_slice', 1, -1, true, true)) 625 eq({ 'b' }, curbuf_depr('get_line_slice', 1, 2, true, false)) 626 eq({}, curbuf_depr('get_line_slice', 1, 1, true, false)) 627 eq({ 'a', 'b' }, curbuf_depr('get_line_slice', 0, -1, true, false)) 628 eq({ 'b' }, curbuf_depr('get_line_slice', 1, -1, true, false)) 629 eq({ 'b', 'c' }, curbuf_depr('get_line_slice', -2, -1, true, true)) 630 curbuf_depr('set_line_slice', 1, 2, true, false, { 'a', 'b', 'c' }) 631 eq({ 'a', 'a', 'b', 'c', 'c' }, curbuf_depr('get_line_slice', 0, -1, true, true)) 632 curbuf_depr('set_line_slice', -1, -1, true, true, { 'a', 'b', 'c' }) 633 eq({ 'a', 'a', 'b', 'c', 'a', 'b', 'c' }, curbuf_depr('get_line_slice', 0, -1, true, true)) 634 curbuf_depr('set_line_slice', 0, -3, true, false, {}) 635 eq({ 'a', 'b', 'c' }, curbuf_depr('get_line_slice', 0, -1, true, true)) 636 curbuf_depr('set_line_slice', 0, -1, true, true, {}) 637 eq({ '' }, curbuf_depr('get_line_slice', 0, -1, true, true)) 638 end) 639 end) 640 641 describe_lua_and_rpc('nvim_buf_get_lines, nvim_buf_set_lines', function(lua_or_rpc) 642 local function get_lines(...) 643 return lua_or_rpc.nvim_buf_get_lines(0, ...) 644 end 645 646 local function set_lines(...) 647 return lua_or_rpc.nvim_buf_set_lines(0, ...) 648 end 649 650 local function line_count() 651 return lua_or_rpc.nvim_buf_line_count(0) 652 end 653 654 it('fails correctly when input is not valid', function() 655 eq(1, lua_or_rpc.nvim_buf_get_number(0)) 656 eq( 657 [['replacement string' item contains newlines]], 658 pcall_err(lua_or_rpc.nvim_buf_set_lines, 1, 1, 2, false, { 'b\na' }) 659 ) 660 end) 661 662 it("fails if 'nomodifiable'", function() 663 command('set nomodifiable') 664 eq( 665 [[Buffer is not 'modifiable']], 666 pcall_err(lua_or_rpc.nvim_buf_set_lines, 1, 1, 2, false, { 'a', 'b' }) 667 ) 668 end) 669 670 it('has correct line_count when inserting and deleting', function() 671 eq(1, line_count()) 672 set_lines(-1, -1, true, { 'line' }) 673 eq(2, line_count()) 674 set_lines(-1, -1, true, { 'line' }) 675 eq(3, line_count()) 676 set_lines(-2, -1, true, {}) 677 eq(2, line_count()) 678 set_lines(-2, -1, true, {}) 679 set_lines(-2, -1, true, {}) 680 -- There's always at least one line 681 eq(1, line_count()) 682 end) 683 684 it('can get, set and delete a single line', function() 685 eq({ '' }, get_lines(0, 1, true)) 686 set_lines(0, 1, true, { 'line1' }) 687 eq({ 'line1' }, get_lines(0, 1, true)) 688 set_lines(0, 1, true, { 'line2' }) 689 eq({ 'line2' }, get_lines(0, 1, true)) 690 set_lines(0, 1, true, {}) 691 eq({ '' }, get_lines(0, 1, true)) 692 end) 693 694 it('can get a single line with strict indexing', function() 695 set_lines(0, 1, true, { 'line1.a' }) 696 eq(1, line_count()) -- sanity 697 eq('Index out of bounds', pcall_err(get_lines, 1, 2, true)) 698 eq('Index out of bounds', pcall_err(get_lines, -3, -2, true)) 699 end) 700 701 it('can get a single line with non-strict indexing', function() 702 set_lines(0, 1, true, { 'line1.a' }) 703 eq(1, line_count()) -- sanity 704 eq({}, get_lines(1, 2, false)) 705 eq({}, get_lines(-3, -2, false)) 706 end) 707 708 it('can set and delete a single line with strict indexing', function() 709 set_lines(0, 1, true, { 'line1.a' }) 710 eq('Index out of bounds', pcall_err(set_lines, 1, 2, true, { 'line1.b' })) 711 eq('Index out of bounds', pcall_err(set_lines, -3, -2, true, { 'line1.c' })) 712 eq({ 'line1.a' }, get_lines(0, -1, true)) 713 eq('Index out of bounds', pcall_err(set_lines, 1, 2, true, {})) 714 eq('Index out of bounds', pcall_err(set_lines, -3, -2, true, {})) 715 eq({ 'line1.a' }, get_lines(0, -1, true)) 716 end) 717 718 it('can set and delete a single line with non-strict indexing', function() 719 set_lines(0, 1, true, { 'line1.a' }) 720 set_lines(1, 2, false, { 'line1.b' }) 721 set_lines(-4, -3, false, { 'line1.c' }) 722 eq({ 'line1.c', 'line1.a', 'line1.b' }, get_lines(0, -1, true)) 723 set_lines(3, 4, false, {}) 724 set_lines(-5, -4, false, {}) 725 eq({ 'line1.c', 'line1.a', 'line1.b' }, get_lines(0, -1, true)) 726 end) 727 728 it('can handle NULs', function() 729 set_lines(0, 1, true, { 'ab\0cd' }) 730 eq({ 'ab\0cd' }, get_lines(0, -1, true)) 731 end) 732 733 it('works with multiple lines', function() 734 eq({ '' }, get_lines(0, -1, true)) 735 -- Replace buffer 736 for _, mode in pairs({ false, true }) do 737 set_lines(0, -1, mode, { 'a', 'b', 'c' }) 738 eq({ 'a', 'b', 'c' }, get_lines(0, -1, mode)) 739 eq({ 'b', 'c' }, get_lines(1, -1, mode)) 740 eq({ 'b' }, get_lines(1, 2, mode)) 741 eq({}, get_lines(1, 1, mode)) 742 eq({ 'a', 'b' }, get_lines(0, -2, mode)) 743 eq({ 'b' }, get_lines(1, -2, mode)) 744 eq({ 'b', 'c' }, get_lines(-3, -1, mode)) 745 set_lines(1, 2, mode, { 'a', 'b', 'c' }) 746 eq({ 'a', 'a', 'b', 'c', 'c' }, get_lines(0, -1, mode)) 747 set_lines(-2, -1, mode, { 'a', 'b', 'c' }) 748 eq({ 'a', 'a', 'b', 'c', 'a', 'b', 'c' }, get_lines(0, -1, mode)) 749 set_lines(0, -4, mode, {}) 750 eq({ 'a', 'b', 'c' }, get_lines(0, -1, mode)) 751 set_lines(0, -1, mode, {}) 752 eq({ '' }, get_lines(0, -1, mode)) 753 end 754 end) 755 756 it('can get line ranges with non-strict indexing', function() 757 set_lines(0, -1, true, { 'a', 'b', 'c' }) 758 eq({ 'a', 'b', 'c' }, get_lines(0, -1, true)) --sanity 759 760 eq({}, get_lines(3, 4, false)) 761 eq({}, get_lines(3, 10, false)) 762 eq({}, get_lines(-5, -5, false)) 763 eq({}, get_lines(3, -1, false)) 764 eq({}, get_lines(-3, -4, false)) 765 end) 766 767 it('can get line ranges with strict indexing', function() 768 set_lines(0, -1, true, { 'a', 'b', 'c' }) 769 eq({ 'a', 'b', 'c' }, get_lines(0, -1, true)) --sanity 770 771 eq('Index out of bounds', pcall_err(get_lines, 3, 4, true)) 772 eq('Index out of bounds', pcall_err(get_lines, 3, 10, true)) 773 eq('Index out of bounds', pcall_err(get_lines, -5, -5, true)) 774 -- empty or inverted ranges are not errors 775 eq({}, get_lines(3, -1, true)) 776 eq({}, get_lines(-3, -4, true)) 777 end) 778 779 it('set_lines: out-of-bounds can extend past end', function() 780 set_lines(0, -1, true, { 'a', 'b', 'c' }) 781 eq({ 'a', 'b', 'c' }, get_lines(0, -1, true)) --sanity 782 783 eq({ 'c' }, get_lines(-2, 5, false)) 784 eq({ 'a', 'b', 'c' }, get_lines(0, 6, false)) 785 eq('Index out of bounds', pcall_err(set_lines, 4, 6, true, { 'd' })) 786 set_lines(4, 6, false, { 'd' }) 787 eq({ 'a', 'b', 'c', 'd' }, get_lines(0, -1, true)) 788 eq('Index out of bounds', pcall_err(set_lines, -6, -6, true, { 'e' })) 789 set_lines(-6, -6, false, { 'e' }) 790 eq({ 'e', 'a', 'b', 'c', 'd' }, get_lines(0, -1, true)) 791 end) 792 793 it('set_lines on alternate buffer does not access invalid line (E315)', function() 794 command('set hidden') 795 insert('Initial file') 796 command('enew') 797 insert([[ 798 More 799 Lines 800 Than 801 In 802 The 803 Other 804 Buffer]]) 805 command('$') 806 eq(true, pcall(api.nvim_buf_set_lines, 0, 0, 1, false, { 'test' })) 807 end) 808 809 it("set_lines of invisible buffer doesn't move cursor in current window", function() 810 local screen = Screen.new(20, 5) 811 812 insert([[ 813 Who would win? 814 A real window 815 with proper text]]) 816 local buf = lua_or_rpc.nvim_create_buf(false, true) 817 screen:expect([[ 818 Who would win? | 819 A real window | 820 with proper tex^t | 821 {1:~ }| 822 | 823 ]]) 824 825 lua_or_rpc.nvim_buf_set_lines(buf, 0, -1, true, { 'or some', 'scratchy text' }) 826 feed('i') -- provoke redraw 827 screen:expect([[ 828 Who would win? | 829 A real window | 830 with proper tex^t | 831 {1:~ }| 832 {5:-- INSERT --} | 833 ]]) 834 end) 835 836 it('set_lines on hidden buffer preserves "previous window" #9741', function() 837 insert([[ 838 visible buffer line 1 839 line 2 840 ]]) 841 local hiddenbuf = lua_or_rpc.nvim_create_buf(false, true) 842 command('vsplit') 843 command('vsplit') 844 feed('<c-w>l<c-w>l<c-w>l') 845 eq(3, fn.winnr()) 846 feed('<c-w>h') 847 eq(2, fn.winnr()) 848 lua_or_rpc.nvim_buf_set_lines(hiddenbuf, 0, -1, true, { 'hidden buffer line 1', 'line 2' }) 849 feed('<c-w>p') 850 eq(3, fn.winnr()) 851 end) 852 853 it('set_lines on unloaded buffer #8659 #22670', function() 854 local bufnr = api.nvim_get_current_buf() 855 lua_or_rpc.nvim_buf_set_lines(bufnr, 0, -1, false, { 'a', 'b', 'c' }) 856 lua_or_rpc.nvim_buf_set_name(bufnr, 'set_lines') 857 finally(function() 858 os.remove('set_lines') 859 end) 860 command('write!') 861 command('new') 862 command('bunload! ' .. bufnr) 863 local new_bufnr = fn.bufnr('set_lines', true) 864 lua_or_rpc.nvim_buf_set_lines(new_bufnr, 0, -1, false, {}) 865 eq({ '' }, lua_or_rpc.nvim_buf_get_lines(new_bufnr, 0, -1, false)) 866 eq(true, api.nvim_buf_is_loaded(new_bufnr)) 867 end) 868 end) 869 870 describe('nvim_buf_set_text', function() 871 local function get_lines(...) 872 return api.nvim_buf_get_lines(0, ...) 873 end 874 875 local function set_text(...) 876 return api.nvim_buf_set_text(0, ...) 877 end 878 879 it('works', function() 880 insert([[ 881 hello foo! 882 text 883 ]]) 884 885 eq({ 'hello foo!' }, get_lines(0, 1, true)) 886 887 -- can replace a single word 888 set_text(0, 6, 0, 9, { 'world' }) 889 eq({ 'hello world!', 'text' }, get_lines(0, 2, true)) 890 891 -- can insert text 892 set_text(0, 0, 0, 0, { 'well ' }) 893 eq({ 'well hello world!', 'text' }, get_lines(0, 2, true)) 894 895 -- can delete text 896 set_text(0, 0, 0, 5, { '' }) 897 eq({ 'hello world!', 'text' }, get_lines(0, 2, true)) 898 899 -- can replace with multiple lines 900 set_text(0, 6, 0, 11, { 'foo', 'wo', 'more' }) 901 eq({ 'hello foo', 'wo', 'more!', 'text' }, get_lines(0, 4, true)) 902 903 -- will join multiple lines if needed 904 set_text(0, 6, 3, 4, { 'bar' }) 905 eq({ 'hello bar' }, get_lines(0, 1, true)) 906 907 -- can use negative line numbers 908 set_text(-2, 0, -2, 5, { 'goodbye' }) 909 eq({ 'goodbye bar', '' }, get_lines(0, -1, true)) 910 911 set_text(-1, 0, -1, 0, { 'text' }) 912 eq({ 'goodbye bar', 'text' }, get_lines(0, 2, true)) 913 914 -- can append to a line 915 set_text(1, 4, -1, 4, { ' and', 'more' }) 916 eq({ 'goodbye bar', 'text and', 'more' }, get_lines(0, 3, true)) 917 918 -- can use negative column numbers 919 set_text(0, -5, 0, -1, { '!' }) 920 eq({ 'goodbye!' }, get_lines(0, 1, true)) 921 end) 922 923 it('works with undo', function() 924 insert([[ 925 hello world! 926 foo bar 927 ]]) 928 929 -- setting text 930 set_text(0, 0, 0, 0, { 'well ' }) 931 feed('u') 932 eq({ 'hello world!' }, get_lines(0, 1, true)) 933 934 -- deleting text 935 set_text(0, 0, 0, 6, { '' }) 936 feed('u') 937 eq({ 'hello world!' }, get_lines(0, 1, true)) 938 939 -- inserting newlines 940 set_text(0, 0, 0, 0, { 'hello', 'mr ' }) 941 feed('u') 942 eq({ 'hello world!' }, get_lines(0, 1, true)) 943 944 -- deleting newlines 945 set_text(0, 0, 1, 4, { 'hello' }) 946 feed('u') 947 eq({ 'hello world!' }, get_lines(0, 1, true)) 948 end) 949 950 it('updates the cursor position', function() 951 insert([[ 952 hello world! 953 ]]) 954 955 -- position the cursor on `!` 956 api.nvim_win_set_cursor(0, { 1, 11 }) 957 -- replace 'world' with 'foo' 958 set_text(0, 6, 0, 11, { 'foo' }) 959 eq('hello foo!', curbuf_depr('get_line', 0)) 960 -- cursor should be moved left by two columns (replacement is shorter by 2 chars) 961 eq({ 1, 9 }, api.nvim_win_get_cursor(0)) 962 end) 963 964 it('updates the cursor position in non-current window', function() 965 insert([[ 966 hello world!]]) 967 968 -- position the cursor on `!` 969 api.nvim_win_set_cursor(0, { 1, 11 }) 970 971 local win = api.nvim_get_current_win() 972 local buf = api.nvim_get_current_buf() 973 974 command('new') 975 976 -- replace 'world' with 'foo' 977 api.nvim_buf_set_text(buf, 0, 6, 0, 11, { 'foo' }) 978 eq({ 'hello foo!' }, api.nvim_buf_get_lines(buf, 0, -1, true)) 979 -- cursor should be moved left by two columns (replacement is shorter by 2 chars) 980 eq({ 1, 9 }, api.nvim_win_get_cursor(win)) 981 end) 982 983 it('updates the cursor position in TWO non-current windows', function() 984 insert([[ 985 hello world!]]) 986 987 -- position the cursor on `!` 988 api.nvim_win_set_cursor(0, { 1, 11 }) 989 local win = api.nvim_get_current_win() 990 local buf = api.nvim_get_current_buf() 991 992 command('split') 993 local win2 = api.nvim_get_current_win() 994 -- position the cursor on `w` 995 api.nvim_win_set_cursor(0, { 1, 6 }) 996 997 command('new') 998 999 -- replace 'hello' with 'foo' 1000 api.nvim_buf_set_text(buf, 0, 0, 0, 5, { 'foo' }) 1001 eq({ 'foo world!' }, api.nvim_buf_get_lines(buf, 0, -1, true)) 1002 1003 -- both cursors should be moved left by two columns (replacement is shorter by 2 chars) 1004 eq({ 1, 9 }, api.nvim_win_get_cursor(win)) 1005 eq({ 1, 4 }, api.nvim_win_get_cursor(win2)) 1006 end) 1007 1008 describe('when text is being added right at cursor position #22526', function() 1009 it('updates the cursor position in NORMAL mode', function() 1010 insert([[ 1011 abcd]]) 1012 1013 -- position the cursor on 'c' 1014 api.nvim_win_set_cursor(0, { 1, 2 }) 1015 -- add 'xxx' before 'c' 1016 set_text(0, 2, 0, 2, { 'xxx' }) 1017 eq({ 'abxxxcd' }, get_lines(0, -1, true)) 1018 -- cursor should be on 'c' 1019 eq({ 1, 5 }, api.nvim_win_get_cursor(0)) 1020 end) 1021 1022 it('updates the cursor position only in non-current window when in INSERT mode', function() 1023 insert([[ 1024 abcd]]) 1025 1026 -- position the cursor on 'c' 1027 api.nvim_win_set_cursor(0, { 1, 2 }) 1028 -- open vertical split 1029 feed('<c-w>v') 1030 -- get into INSERT mode to treat cursor 1031 -- as being after 'b', not on 'c' 1032 feed('i') 1033 -- add 'xxx' between 'b' and 'c' 1034 set_text(0, 2, 0, 2, { 'xxx' }) 1035 eq({ 'abxxxcd' }, get_lines(0, -1, true)) 1036 -- in the current window cursor should stay after 'b' 1037 eq({ 1, 2 }, api.nvim_win_get_cursor(0)) 1038 -- quit INSERT mode 1039 feed('<esc>') 1040 -- close current window 1041 feed('<c-w>c') 1042 -- in another window cursor should be on 'c' 1043 eq({ 1, 5 }, api.nvim_win_get_cursor(0)) 1044 end) 1045 end) 1046 1047 describe('when text is being deleted right at cursor position', function() 1048 it('leaves cursor at the same position in NORMAL mode', function() 1049 insert([[ 1050 abcd]]) 1051 1052 -- position the cursor on 'b' 1053 api.nvim_win_set_cursor(0, { 1, 1 }) 1054 -- delete 'b' 1055 set_text(0, 1, 0, 2, {}) 1056 eq({ 'acd' }, get_lines(0, -1, true)) 1057 -- cursor is now on 'c' 1058 eq({ 1, 1 }, api.nvim_win_get_cursor(0)) 1059 end) 1060 1061 it('maintains INSERT-mode cursor position current/non-current window', function() 1062 insert([[ 1063 abcd]]) 1064 1065 -- position the cursor on 'b' 1066 api.nvim_win_set_cursor(0, { 1, 1 }) 1067 -- open vertical split 1068 feed('<c-w>v') 1069 -- get into INSERT mode to treat cursor 1070 -- as being after 'a', not on 'b' 1071 feed('i') 1072 -- delete 'b' 1073 set_text(0, 1, 0, 2, {}) 1074 eq({ 'acd' }, get_lines(0, -1, true)) 1075 -- cursor in the current window should stay after 'a' 1076 eq({ 1, 1 }, api.nvim_win_get_cursor(0)) 1077 -- quit INSERT mode 1078 feed('<esc>') 1079 -- close current window 1080 feed('<c-w>c') 1081 -- cursor in non-current window should stay on 'c' 1082 eq({ 1, 1 }, api.nvim_win_get_cursor(0)) 1083 end) 1084 end) 1085 1086 describe('when cursor is inside replaced row range', function() 1087 it('maintains cursor position if at start_row, but before start_col', function() 1088 insert([[ 1089 This should be first 1090 then there is a line we do not want 1091 and finally the last one]]) 1092 1093 -- position the cursor on ' ' before 'first' 1094 api.nvim_win_set_cursor(0, { 1, 14 }) 1095 1096 set_text(0, 15, 2, 11, { 1097 'the line we do not want', 1098 'but hopefully', 1099 }) 1100 1101 eq({ 1102 'This should be the line we do not want', 1103 'but hopefully the last one', 1104 }, get_lines(0, -1, true)) 1105 -- cursor should stay at the same position 1106 eq({ 1, 14 }, api.nvim_win_get_cursor(0)) 1107 end) 1108 1109 it('maintains cursor position if at start_row and column is still valid', function() 1110 insert([[ 1111 This should be first 1112 then there is a line we do not want 1113 and finally the last one]]) 1114 1115 -- position the cursor on 'f' in 'first' 1116 api.nvim_win_set_cursor(0, { 1, 15 }) 1117 1118 set_text(0, 15, 2, 11, { 1119 'the line we do not want', 1120 'but hopefully', 1121 }) 1122 1123 eq({ 1124 'This should be the line we do not want', 1125 'but hopefully the last one', 1126 }, get_lines(0, -1, true)) 1127 -- cursor should stay at the same position 1128 eq({ 1, 15 }, api.nvim_win_get_cursor(0)) 1129 end) 1130 1131 it('adjusts cursor column to keep it valid if start_row got smaller', function() 1132 insert([[ 1133 This should be first 1134 then there is a line we do not want 1135 and finally the last one]]) 1136 1137 -- position the cursor on 't' in 'first' 1138 api.nvim_win_set_cursor(0, { 1, 19 }) 1139 1140 local cursor = exec_lua([[ 1141 vim.api.nvim_buf_set_text(0, 0, 15, 2, 24, {'last'}) 1142 return vim.api.nvim_win_get_cursor(0) 1143 ]]) 1144 1145 eq({ 'This should be last' }, get_lines(0, -1, true)) 1146 -- cursor should end up on 't' in 'last' 1147 eq({ 1, 18 }, api.nvim_win_get_cursor(0)) 1148 -- immediate call to nvim_win_get_cursor should have returned the same position 1149 eq({ 1, 18 }, cursor) 1150 end) 1151 1152 it('adjusts cursor column to keep it valid if start_row decreased in INSERT mode', function() 1153 insert([[ 1154 This should be first 1155 then there is a line we do not want 1156 and finally the last one]]) 1157 1158 -- position the cursor on 't' in 'first' 1159 api.nvim_win_set_cursor(0, { 1, 19 }) 1160 -- enter INSERT mode to treat cursor as being after 't' 1161 feed('a') 1162 1163 local cursor = exec_lua([[ 1164 vim.api.nvim_buf_set_text(0, 0, 15, 2, 24, {'last'}) 1165 return vim.api.nvim_win_get_cursor(0) 1166 ]]) 1167 1168 eq({ 'This should be last' }, get_lines(0, -1, true)) 1169 -- cursor should end up after 't' in 'last' 1170 eq({ 1, 19 }, api.nvim_win_get_cursor(0)) 1171 -- immediate call to nvim_win_get_cursor should have returned the same position 1172 eq({ 1, 19 }, cursor) 1173 end) 1174 1175 it('adjusts cursor to valid column in row after start_row if it got smaller', function() 1176 insert([[ 1177 This should be first 1178 then there is a line we do not want 1179 and finally the last one]]) 1180 1181 -- position the cursor on 'w' in 'want' 1182 api.nvim_win_set_cursor(0, { 2, 31 }) 1183 1184 local cursor = exec_lua([[ 1185 vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, { 1186 '1', 1187 'then 2', 1188 'and then', 1189 }) 1190 return vim.api.nvim_win_get_cursor(0) 1191 ]]) 1192 1193 eq({ 1194 'This should be 1', 1195 'then 2', 1196 'and then the last one', 1197 }, get_lines(0, -1, true)) 1198 -- cursor column should end up at the end of a row 1199 eq({ 2, 5 }, api.nvim_win_get_cursor(0)) 1200 -- immediate call to nvim_win_get_cursor should have returned the same position 1201 eq({ 2, 5 }, cursor) 1202 end) 1203 1204 it( 1205 'adjusts cursor to valid column in row after start_row if it got smaller in INSERT mode', 1206 function() 1207 insert([[ 1208 This should be first 1209 then there is a line we do not want 1210 and finally the last one]]) 1211 1212 -- position the cursor on 'w' in 'want' 1213 api.nvim_win_set_cursor(0, { 2, 31 }) 1214 -- enter INSERT mode 1215 feed('a') 1216 1217 local cursor = exec_lua([[ 1218 vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, { 1219 '1', 1220 'then 2', 1221 'and then', 1222 }) 1223 return vim.api.nvim_win_get_cursor(0) 1224 ]]) 1225 1226 eq({ 1227 'This should be 1', 1228 'then 2', 1229 'and then the last one', 1230 }, get_lines(0, -1, true)) 1231 -- cursor column should end up at the end of a row 1232 eq({ 2, 6 }, api.nvim_win_get_cursor(0)) 1233 -- immediate call to nvim_win_get_cursor should have returned the same position 1234 eq({ 2, 6 }, cursor) 1235 end 1236 ) 1237 1238 it('adjusts cursor line and column to keep it inside replacement range', function() 1239 insert([[ 1240 This should be first 1241 then there is a line we do not want 1242 and finally the last one]]) 1243 1244 -- position the cursor on 'n' in 'finally' 1245 api.nvim_win_set_cursor(0, { 3, 6 }) 1246 1247 local cursor = exec_lua([[ 1248 vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, { 1249 'the line we do not want', 1250 'but hopefully', 1251 }) 1252 return vim.api.nvim_win_get_cursor(0) 1253 ]]) 1254 1255 eq({ 1256 'This should be the line we do not want', 1257 'but hopefully the last one', 1258 }, get_lines(0, -1, true)) 1259 -- cursor should end up on 'y' in 'hopefully' 1260 -- to stay in the range, because it got smaller 1261 eq({ 2, 12 }, api.nvim_win_get_cursor(0)) 1262 -- immediate call to nvim_win_get_cursor should have returned the same position 1263 eq({ 2, 12 }, cursor) 1264 end) 1265 1266 it('adjusts cursor line and column if replacement is empty', function() 1267 insert([[ 1268 This should be first 1269 then there is a line we do not want 1270 and finally the last one]]) 1271 1272 -- position the cursor on 'r' in 'there' 1273 api.nvim_win_set_cursor(0, { 2, 8 }) 1274 1275 local cursor = exec_lua([[ 1276 vim.api.nvim_buf_set_text(0, 0, 15, 2, 12, {}) 1277 return vim.api.nvim_win_get_cursor(0) 1278 ]]) 1279 1280 eq({ 'This should be the last one' }, get_lines(0, -1, true)) 1281 -- cursor should end up on the next column after deleted range 1282 eq({ 1, 15 }, api.nvim_win_get_cursor(0)) 1283 -- immediate call to nvim_win_get_cursor should have returned the same position 1284 eq({ 1, 15 }, cursor) 1285 end) 1286 1287 it('adjusts cursor line and column if replacement is empty and start_col == 0', function() 1288 insert([[ 1289 This should be first 1290 then there is a line we do not want 1291 and finally the last one]]) 1292 1293 -- position the cursor on 'r' in 'there' 1294 api.nvim_win_set_cursor(0, { 2, 8 }) 1295 1296 local cursor = exec_lua([[ 1297 vim.api.nvim_buf_set_text(0, 0, 0, 2, 4, {}) 1298 return vim.api.nvim_win_get_cursor(0) 1299 ]]) 1300 1301 eq({ 'finally the last one' }, get_lines(0, -1, true)) 1302 -- cursor should end up in column 0 1303 eq({ 1, 0 }, api.nvim_win_get_cursor(0)) 1304 -- immediate call to nvim_win_get_cursor should have returned the same position 1305 eq({ 1, 0 }, cursor) 1306 end) 1307 1308 it('adjusts cursor column if replacement ends at cursor row, after cursor column', function() 1309 insert([[ 1310 This should be first 1311 then there is a line we do not want 1312 and finally the last one]]) 1313 1314 -- position the cursor on 'y' in 'finally' 1315 api.nvim_win_set_cursor(0, { 3, 10 }) 1316 set_text(0, 15, 2, 11, { '1', 'this 2', 'and then' }) 1317 1318 eq({ 1319 'This should be 1', 1320 'this 2', 1321 'and then the last one', 1322 }, get_lines(0, -1, true)) 1323 -- cursor should end up on 'n' in 'then' 1324 eq({ 3, 7 }, api.nvim_win_get_cursor(0)) 1325 end) 1326 1327 it( 1328 'adjusts cursor column if replacement ends at cursor row, at cursor column in INSERT mode', 1329 function() 1330 insert([[ 1331 This should be first 1332 then there is a line we do not want 1333 and finally the last one]]) 1334 1335 -- position the cursor on 'y' at 'finally' 1336 api.nvim_win_set_cursor(0, { 3, 10 }) 1337 -- enter INSERT mode to treat cursor as being between 'l' and 'y' 1338 feed('i') 1339 set_text(0, 15, 2, 11, { '1', 'this 2', 'and then' }) 1340 1341 eq({ 1342 'This should be 1', 1343 'this 2', 1344 'and then the last one', 1345 }, get_lines(0, -1, true)) 1346 -- cursor should end up after 'n' in 'then' 1347 eq({ 3, 8 }, api.nvim_win_get_cursor(0)) 1348 end 1349 ) 1350 1351 it('adjusts cursor column if replacement is inside of a single line', function() 1352 insert([[ 1353 This should be first 1354 then there is a line we do not want 1355 and finally the last one]]) 1356 1357 -- position the cursor on 'y' in 'finally' 1358 api.nvim_win_set_cursor(0, { 3, 10 }) 1359 set_text(2, 4, 2, 11, { 'then' }) 1360 1361 eq({ 1362 'This should be first', 1363 'then there is a line we do not want', 1364 'and then the last one', 1365 }, get_lines(0, -1, true)) 1366 -- cursor should end up on 'n' in 'then' 1367 eq({ 3, 7 }, api.nvim_win_get_cursor(0)) 1368 end) 1369 1370 it('does not move cursor column after end of a line', function() 1371 insert([[ 1372 This should be the only line here 1373 !!!]]) 1374 1375 -- position cursor on the last '1' 1376 api.nvim_win_set_cursor(0, { 2, 2 }) 1377 1378 local cursor = exec_lua([[ 1379 vim.api.nvim_buf_set_text(0, 0, 33, 1, 3, {}) 1380 return vim.api.nvim_win_get_cursor(0) 1381 ]]) 1382 1383 eq({ 'This should be the only line here' }, get_lines(0, -1, true)) 1384 -- cursor should end up on '!' 1385 eq({ 1, 32 }, api.nvim_win_get_cursor(0)) 1386 -- immediate call to nvim_win_get_cursor should have returned the same position 1387 eq({ 1, 32 }, cursor) 1388 end) 1389 1390 it('does not move cursor column before start of a line', function() 1391 insert('\n!!!') 1392 1393 -- position cursor on the last '1' 1394 api.nvim_win_set_cursor(0, { 2, 2 }) 1395 1396 local cursor = exec_lua([[ 1397 vim.api.nvim_buf_set_text(0, 0, 0, 1, 3, {}) 1398 return vim.api.nvim_win_get_cursor(0) 1399 ]]) 1400 1401 eq({ '' }, get_lines(0, -1, true)) 1402 -- cursor should end up on '!' 1403 eq({ 1, 0 }, api.nvim_win_get_cursor(0)) 1404 -- immediate call to nvim_win_get_cursor should have returned the same position 1405 eq({ 1, 0 }, cursor) 1406 end) 1407 1408 describe('with virtualedit', function() 1409 it('adjusts cursor line/col to keep inside replacement range if not after eol', function() 1410 insert([[ 1411 This should be first 1412 then there is a line we do not want 1413 and finally the last one]]) 1414 1415 -- position cursor on 't' in 'want' 1416 api.nvim_win_set_cursor(0, { 2, 34 }) 1417 -- turn on virtualedit 1418 command('set virtualedit=all') 1419 1420 local cursor = exec_lua([[ 1421 vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, { 1422 'the line we do not want', 1423 'but hopefully', 1424 }) 1425 return vim.api.nvim_win_get_cursor(0) 1426 ]]) 1427 1428 eq({ 1429 'This should be the line we do not want', 1430 'but hopefully the last one', 1431 }, get_lines(0, -1, true)) 1432 -- cursor should end up on 'y' in 'hopefully' 1433 -- to stay in the range 1434 eq({ 2, 12 }, api.nvim_win_get_cursor(0)) 1435 -- immediate call to nvim_win_get_cursor should have returned the same position 1436 eq({ 2, 12 }, cursor) 1437 -- coladd should be 0 1438 eq(0, fn.winsaveview().coladd) 1439 end) 1440 1441 it('does not change cursor screen column when cursor >EOL and row got shorter', function() 1442 insert([[ 1443 This should be first 1444 then there is a line we do not want 1445 and finally the last one]]) 1446 1447 -- position cursor on 't' in 'want' 1448 api.nvim_win_set_cursor(0, { 2, 34 }) 1449 -- turn on virtualedit 1450 command('set virtualedit=all') 1451 -- move cursor after eol 1452 fn.winrestview({ coladd = 5 }) 1453 1454 local cursor = exec_lua([[ 1455 vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, { 1456 'the line we do not want', 1457 'but hopefully', 1458 }) 1459 return vim.api.nvim_win_get_cursor(0) 1460 ]]) 1461 1462 eq({ 1463 'This should be the line we do not want', 1464 'but hopefully the last one', 1465 }, get_lines(0, -1, true)) 1466 -- cursor should end up at eol of a new row 1467 eq({ 2, 26 }, api.nvim_win_get_cursor(0)) 1468 -- immediate call to nvim_win_get_cursor should have returned the same position 1469 eq({ 2, 26 }, cursor) 1470 -- coladd should be increased so that cursor stays in the same screen column 1471 eq(13, fn.winsaveview().coladd) 1472 end) 1473 1474 it( 1475 'does not change cursor screen column when cursor is after eol and row got longer', 1476 function() 1477 insert([[ 1478 This should be first 1479 then there is a line we do not want 1480 and finally the last one]]) 1481 1482 -- position cursor on 't' in 'first' 1483 api.nvim_win_set_cursor(0, { 1, 19 }) 1484 -- turn on virtualedit 1485 command('set virtualedit=all') 1486 -- move cursor after eol 1487 fn.winrestview({ coladd = 21 }) 1488 1489 local cursor = exec_lua([[ 1490 vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, { 1491 'the line we do not want', 1492 'but hopefully', 1493 }) 1494 return vim.api.nvim_win_get_cursor(0) 1495 ]]) 1496 1497 eq({ 1498 'This should be the line we do not want', 1499 'but hopefully the last one', 1500 }, get_lines(0, -1, true)) 1501 -- cursor should end up at eol of a new row 1502 eq({ 1, 38 }, api.nvim_win_get_cursor(0)) 1503 -- immediate call to nvim_win_get_cursor should have returned the same position 1504 eq({ 1, 38 }, cursor) 1505 -- coladd should be increased so that cursor stays in the same screen column 1506 eq(2, fn.winsaveview().coladd) 1507 end 1508 ) 1509 1510 it( 1511 'does not change cursor screen column when cursor is after eol and row extended past cursor column', 1512 function() 1513 insert([[ 1514 This should be first 1515 then there is a line we do not want 1516 and finally the last one]]) 1517 1518 -- position cursor on 't' in 'first' 1519 api.nvim_win_set_cursor(0, { 1, 19 }) 1520 -- turn on virtualedit 1521 command('set virtualedit=all') 1522 -- move cursor after eol just a bit 1523 fn.winrestview({ coladd = 3 }) 1524 1525 local cursor = exec_lua([[ 1526 vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, { 1527 'the line we do not want', 1528 'but hopefully', 1529 }) 1530 return vim.api.nvim_win_get_cursor(0) 1531 ]]) 1532 1533 eq({ 1534 'This should be the line we do not want', 1535 'but hopefully the last one', 1536 }, get_lines(0, -1, true)) 1537 -- cursor should stay at the same screen column 1538 eq({ 1, 22 }, api.nvim_win_get_cursor(0)) 1539 -- immediate call to nvim_win_get_cursor should have returned the same position 1540 eq({ 1, 22 }, cursor) 1541 -- coladd should become 0 1542 eq(0, fn.winsaveview().coladd) 1543 end 1544 ) 1545 1546 it( 1547 'does not change cursor screen column when cursor is after eol and row range decreased', 1548 function() 1549 insert([[ 1550 This should be first 1551 then there is a line we do not want 1552 and one more 1553 and finally the last one]]) 1554 1555 -- position cursor on 'e' in 'more' 1556 api.nvim_win_set_cursor(0, { 3, 11 }) 1557 -- turn on virtualedit 1558 command('set virtualedit=all') 1559 -- move cursor after eol 1560 fn.winrestview({ coladd = 28 }) 1561 1562 local cursor = exec_lua([[ 1563 vim.api.nvim_buf_set_text(0, 0, 15, 3, 11, { 1564 'the line we do not want', 1565 'but hopefully', 1566 }) 1567 return vim.api.nvim_win_get_cursor(0) 1568 ]]) 1569 1570 eq({ 1571 'This should be the line we do not want', 1572 'but hopefully the last one', 1573 }, get_lines(0, -1, true)) 1574 -- cursor should end up at eol of a new row 1575 eq({ 2, 26 }, api.nvim_win_get_cursor(0)) 1576 -- immediate call to nvim_win_get_cursor should have returned the same position 1577 eq({ 2, 26 }, cursor) 1578 -- coladd should be increased so that cursor stays in the same screen column 1579 eq(13, fn.winsaveview().coladd) 1580 end 1581 ) 1582 end) 1583 end) 1584 1585 describe('when cursor is at end_row and after end_col', function() 1586 it('adjusts cursor column when only a newline is added or deleted', function() 1587 insert([[ 1588 first line 1589 second 1590 line]]) 1591 1592 -- position the cursor on 'i' 1593 api.nvim_win_set_cursor(0, { 3, 2 }) 1594 set_text(1, 6, 2, 0, {}) 1595 eq({ 'first line', 'second line' }, get_lines(0, -1, true)) 1596 -- cursor should stay on 'i' 1597 eq({ 2, 8 }, api.nvim_win_get_cursor(0)) 1598 1599 -- add a newline back 1600 set_text(1, 6, 1, 6, { '', '' }) 1601 eq({ 'first line', 'second', ' line' }, get_lines(0, -1, true)) 1602 -- cursor should return back to the original position 1603 eq({ 3, 2 }, api.nvim_win_get_cursor(0)) 1604 end) 1605 1606 it( 1607 'adjusts cursor column if the range is not bound to either start or end of a line', 1608 function() 1609 insert([[ 1610 This should be first 1611 then there is a line we do not want 1612 and finally the last one]]) 1613 1614 -- position the cursor on 'h' in 'the' 1615 api.nvim_win_set_cursor(0, { 3, 13 }) 1616 set_text(0, 14, 2, 11, {}) 1617 eq({ 'This should be the last one' }, get_lines(0, -1, true)) 1618 -- cursor should stay on 'h' 1619 eq({ 1, 16 }, api.nvim_win_get_cursor(0)) 1620 -- add deleted lines back 1621 set_text(0, 14, 0, 14, { 1622 ' first', 1623 'then there is a line we do not want', 1624 'and finally', 1625 }) 1626 eq({ 1627 'This should be first', 1628 'then there is a line we do not want', 1629 'and finally the last one', 1630 }, get_lines(0, -1, true)) 1631 -- cursor should return back to the original position 1632 eq({ 3, 13 }, api.nvim_win_get_cursor(0)) 1633 end 1634 ) 1635 1636 it( 1637 'adjusts cursor column if replacing lines in range, not just deleting and adding', 1638 function() 1639 insert([[ 1640 This should be first 1641 then there is a line we do not want 1642 and finally the last one]]) 1643 1644 -- position the cursor on 's' in 'last' 1645 api.nvim_win_set_cursor(0, { 3, 18 }) 1646 set_text(0, 15, 2, 11, { 1647 'the line we do not want', 1648 'but hopefully', 1649 }) 1650 1651 eq({ 1652 'This should be the line we do not want', 1653 'but hopefully the last one', 1654 }, get_lines(0, -1, true)) 1655 -- cursor should stay on 's' 1656 eq({ 2, 20 }, api.nvim_win_get_cursor(0)) 1657 1658 set_text(0, 15, 1, 13, { 1659 'first', 1660 'then there is a line we do not want', 1661 'and finally', 1662 }) 1663 1664 eq({ 1665 'This should be first', 1666 'then there is a line we do not want', 1667 'and finally the last one', 1668 }, get_lines(0, -1, true)) 1669 -- cursor should return back to the original position 1670 eq({ 3, 18 }, api.nvim_win_get_cursor(0)) 1671 end 1672 ) 1673 1674 it('does not move cursor column after end of a line', function() 1675 insert([[ 1676 This should be the only line here 1677 ]]) 1678 1679 -- position cursor at the empty line 1680 api.nvim_win_set_cursor(0, { 2, 0 }) 1681 1682 local cursor = exec_lua([[ 1683 vim.api.nvim_buf_set_text(0, 0, 33, 1, 0, {'!'}) 1684 return vim.api.nvim_win_get_cursor(0) 1685 ]]) 1686 1687 eq({ 'This should be the only line here!' }, get_lines(0, -1, true)) 1688 -- cursor should end up on '!' 1689 eq({ 1, 33 }, api.nvim_win_get_cursor(0)) 1690 -- immediate call to nvim_win_get_cursor should have returned the same position 1691 eq({ 1, 33 }, cursor) 1692 end) 1693 1694 it('does not move cursor column before start of a line', function() 1695 insert('\n') 1696 1697 eq({ '', '' }, get_lines(0, -1, true)) 1698 1699 -- position cursor on the last '1' 1700 api.nvim_win_set_cursor(0, { 2, 2 }) 1701 1702 local cursor = exec_lua([[ 1703 vim.api.nvim_buf_set_text(0, 0, 0, 1, 0, {''}) 1704 return vim.api.nvim_win_get_cursor(0) 1705 ]]) 1706 1707 eq({ '' }, get_lines(0, -1, true)) 1708 -- cursor should end up on '!' 1709 eq({ 1, 0 }, api.nvim_win_get_cursor(0)) 1710 -- immediate call to nvim_win_get_cursor should have returned the same position 1711 eq({ 1, 0 }, cursor) 1712 end) 1713 end) 1714 1715 it('can handle NULs', function() 1716 set_text(0, 0, 0, 0, { 'ab\0cd' }) 1717 eq('ab\0cd', curbuf_depr('get_line', 0)) 1718 end) 1719 1720 it('adjusts extmarks', function() 1721 local ns = api.nvim_create_namespace('my-fancy-plugin') 1722 insert([[ 1723 foo bar 1724 baz 1725 ]]) 1726 local id1 = api.nvim_buf_set_extmark(0, ns, 0, 1, {}) 1727 local id2 = api.nvim_buf_set_extmark(0, ns, 0, 7, {}) 1728 local id3 = api.nvim_buf_set_extmark(0, ns, 1, 1, {}) 1729 set_text(0, 4, 0, 7, { 'q' }) 1730 1731 eq({ 'foo q', 'baz' }, get_lines(0, 2, true)) 1732 -- mark before replacement point is unaffected 1733 eq({ 0, 1 }, api.nvim_buf_get_extmark_by_id(0, ns, id1, {})) 1734 -- mark gets shifted back because the replacement was shorter 1735 eq({ 0, 5 }, api.nvim_buf_get_extmark_by_id(0, ns, id2, {})) 1736 -- mark on the next line is unaffected 1737 eq({ 1, 1 }, api.nvim_buf_get_extmark_by_id(0, ns, id3, {})) 1738 1739 -- replacing the text spanning two lines will adjust the mark on the next line 1740 set_text(0, 3, 1, 3, { 'qux' }) 1741 eq({ 'fooqux', '' }, get_lines(0, 2, true)) 1742 eq({ 0, 6 }, api.nvim_buf_get_extmark_by_id(0, ns, id3, {})) 1743 -- but mark before replacement point is still unaffected 1744 eq({ 0, 1 }, api.nvim_buf_get_extmark_by_id(0, ns, id1, {})) 1745 -- and the mark in the middle was shifted to the end of the insertion 1746 eq({ 0, 6 }, api.nvim_buf_get_extmark_by_id(0, ns, id2, {})) 1747 1748 -- marks should be put back into the same place after undoing 1749 set_text(0, 0, 0, 2, { '' }) 1750 feed('u') 1751 eq({ 0, 1 }, api.nvim_buf_get_extmark_by_id(0, ns, id1, {})) 1752 eq({ 0, 6 }, api.nvim_buf_get_extmark_by_id(0, ns, id2, {})) 1753 eq({ 0, 6 }, api.nvim_buf_get_extmark_by_id(0, ns, id3, {})) 1754 1755 -- marks should be shifted over by the correct number of bytes for multibyte 1756 -- chars 1757 set_text(0, 0, 0, 0, { 'Ø' }) 1758 eq({ 0, 3 }, api.nvim_buf_get_extmark_by_id(0, ns, id1, {})) 1759 eq({ 0, 8 }, api.nvim_buf_get_extmark_by_id(0, ns, id2, {})) 1760 eq({ 0, 8 }, api.nvim_buf_get_extmark_by_id(0, ns, id3, {})) 1761 end) 1762 1763 it('correctly marks changed region for redraw #13890', function() 1764 local screen = Screen.new(20, 5) 1765 1766 insert([[ 1767 AAA 1768 BBB 1769 ]]) 1770 1771 api.nvim_buf_set_text(0, 0, 0, 1, 3, { 'XXX', 'YYY' }) 1772 1773 screen:expect([[ 1774 XXX | 1775 YYY | 1776 ^ | 1777 {1:~ }| 1778 | 1779 ]]) 1780 end) 1781 1782 it('errors on out-of-range', function() 1783 insert([[ 1784 hello foo! 1785 text]]) 1786 eq("Invalid 'start_row': out of range", pcall_err(set_text, 2, 0, 3, 0, {})) 1787 eq("Invalid 'start_row': out of range", pcall_err(set_text, -3, 0, 0, 0, {})) 1788 eq("Invalid 'end_row': out of range", pcall_err(set_text, 0, 0, 2, 0, {})) 1789 eq("Invalid 'end_row': out of range", pcall_err(set_text, 0, 0, -3, 0, {})) 1790 eq("Invalid 'start_col': out of range", pcall_err(set_text, 1, 5, 1, 5, {})) 1791 eq("Invalid 'end_col': out of range", pcall_err(set_text, 1, 0, 1, 5, {})) 1792 end) 1793 1794 it('errors when start is greater than end', function() 1795 insert([[ 1796 hello foo! 1797 text]]) 1798 eq("'start' is higher than 'end'", pcall_err(set_text, 1, 0, 0, 0, {})) 1799 eq("'start' is higher than 'end'", pcall_err(set_text, 0, 1, 0, 0, {})) 1800 end) 1801 1802 it('no heap-use-after-free when called consecutively #19643', function() 1803 set_text(0, 0, 0, 0, { 'one', '', '', 'two' }) 1804 eq({ 'one', '', '', 'two' }, get_lines(0, 4, true)) 1805 api.nvim_win_set_cursor(0, { 1, 0 }) 1806 exec_lua([[ 1807 vim.api.nvim_buf_set_text(0, 0, 3, 1, 0, {''}) 1808 vim.api.nvim_buf_set_text(0, 0, 3, 1, 0, {''}) 1809 ]]) 1810 eq({ 'one', 'two' }, get_lines(0, 2, true)) 1811 end) 1812 1813 it('auto-loads unloaded buffer', function() 1814 local new_bufnr = fn.bufnr('set_text', true) 1815 eq(false, api.nvim_buf_is_loaded(new_bufnr)) 1816 api.nvim_buf_set_text(new_bufnr, 0, 0, 0, -1, { 'foo' }) 1817 eq(true, api.nvim_buf_is_loaded(new_bufnr)) 1818 eq({ 'foo' }, api.nvim_buf_get_lines(new_bufnr, 0, -1, false)) 1819 end) 1820 1821 describe('handles topline', function() 1822 local screen 1823 before_each(function() 1824 screen = Screen.new(20, 12) 1825 api.nvim_buf_set_lines( 1826 0, 1827 0, 1828 -1, 1829 true, 1830 { 'aaa', 'bbb', 'ccc', 'ddd', 'www', 'xxx', 'yyy', 'zzz' } 1831 ) 1832 api.nvim_set_option_value('modified', false, {}) 1833 end) 1834 1835 it('of current window', function() 1836 local win = api.nvim_get_current_win() 1837 local buf = api.nvim_get_current_buf() 1838 1839 command('new | wincmd w') 1840 api.nvim_win_set_cursor(win, { 8, 0 }) 1841 1842 screen:expect { 1843 grid = [[ 1844 | 1845 {1:~ }|*4 1846 {2:[No Name] }| 1847 www | 1848 xxx | 1849 yyy | 1850 ^zzz | 1851 {3:[No Name] }| 1852 | 1853 ]], 1854 } 1855 api.nvim_buf_set_text(buf, 0, 3, 1, 0, { 'X' }) 1856 1857 screen:expect { 1858 grid = [[ 1859 | 1860 {1:~ }|*4 1861 {2:[No Name] }| 1862 www | 1863 xxx | 1864 yyy | 1865 ^zzz | 1866 {3:[No Name] [+] }| 1867 | 1868 ]], 1869 } 1870 end) 1871 1872 it('of non-current window', function() 1873 local win = api.nvim_get_current_win() 1874 local buf = api.nvim_get_current_buf() 1875 1876 command('new') 1877 api.nvim_win_set_cursor(win, { 8, 0 }) 1878 1879 screen:expect { 1880 grid = [[ 1881 ^ | 1882 {1:~ }|*4 1883 {3:[No Name] }| 1884 www | 1885 xxx | 1886 yyy | 1887 zzz | 1888 {2:[No Name] }| 1889 | 1890 ]], 1891 } 1892 1893 api.nvim_buf_set_text(buf, 0, 3, 1, 0, { 'X' }) 1894 screen:expect { 1895 grid = [[ 1896 ^ | 1897 {1:~ }|*4 1898 {3:[No Name] }| 1899 www | 1900 xxx | 1901 yyy | 1902 zzz | 1903 {2:[No Name] [+] }| 1904 | 1905 ]], 1906 } 1907 end) 1908 1909 it('of split windows with same buffer', function() 1910 local win = api.nvim_get_current_win() 1911 local buf = api.nvim_get_current_buf() 1912 1913 command('split') 1914 api.nvim_win_set_cursor(win, { 8, 0 }) 1915 api.nvim_win_set_cursor(0, { 1, 1 }) 1916 1917 screen:expect { 1918 grid = [[ 1919 a^aa | 1920 bbb | 1921 ccc | 1922 ddd | 1923 www | 1924 {3:[No Name] }| 1925 www | 1926 xxx | 1927 yyy | 1928 zzz | 1929 {2:[No Name] }| 1930 | 1931 ]], 1932 } 1933 api.nvim_buf_set_text(buf, 0, 3, 1, 0, { 'X' }) 1934 1935 screen:expect { 1936 grid = [[ 1937 a^aaXbbb | 1938 ccc | 1939 ddd | 1940 www | 1941 xxx | 1942 {3:[No Name] [+] }| 1943 www | 1944 xxx | 1945 yyy | 1946 zzz | 1947 {2:[No Name] [+] }| 1948 | 1949 ]], 1950 } 1951 end) 1952 1953 describe('of current window when', function() 1954 before_each(function() 1955 command('new | wincmd w | setlocal modified') 1956 feed('Gk') 1957 screen:expect([[ 1958 | 1959 {1:~ }|*4 1960 {2:[No Name] }| 1961 www | 1962 xxx | 1963 ^yyy | 1964 zzz | 1965 {3:[No Name] [+] }| 1966 | 1967 ]]) 1968 end) 1969 1970 it('deleting 3 lines around topline', function() 1971 api.nvim_buf_set_text(0, 3, 0, 6, 0, {}) 1972 screen:expect([[ 1973 | 1974 {1:~ }|*4 1975 {2:[No Name] }| 1976 ccc | 1977 ^yyy | 1978 zzz | 1979 {1:~ }| 1980 {3:[No Name] [+] }| 1981 | 1982 ]]) 1983 eq(5, api.nvim_buf_line_count(0)) 1984 end) 1985 1986 it('deleting 3 lines around the line just before topline', function() 1987 api.nvim_buf_set_text(0, 2, 0, 5, 0, {}) 1988 screen:expect([[ 1989 | 1990 {1:~ }|*4 1991 {2:[No Name] }| 1992 bbb | 1993 xxx | 1994 ^yyy | 1995 zzz | 1996 {3:[No Name] [+] }| 1997 | 1998 ]]) 1999 eq(5, api.nvim_buf_line_count(0)) 2000 end) 2001 2002 for count = 1, 4 do 2003 it(('deleting %d lines just before topline'):format(count), function() 2004 api.nvim_buf_set_text(0, 4 - count, 0, 4, 0, {}) 2005 screen:expect_unchanged() 2006 eq(8 - count, api.nvim_buf_line_count(0)) 2007 end) 2008 2009 describe(('replacing %d lines just before topline with 2 lines'):format(count), function() 2010 it('including final newline', function() 2011 api.nvim_buf_set_text(0, 4 - count, 0, 4, 0, { 'eee', 'fff', '' }) 2012 screen:expect_unchanged() 2013 eq(8 - count + 2, api.nvim_buf_line_count(0)) 2014 end) 2015 2016 it('excluding final newline', function() 2017 api.nvim_buf_set_text(0, 4 - count, 0, 3, -1, { 'eee', 'fff' }) 2018 screen:expect_unchanged() 2019 eq(8 - count + 2, api.nvim_buf_line_count(0)) 2020 end) 2021 end) 2022 end 2023 2024 for count = 1, 3 do 2025 it(('deleting %d lines far before topline'):format(count), function() 2026 api.nvim_buf_set_text(0, 0, 0, count, 0, {}) 2027 screen:expect_unchanged() 2028 eq(8 - count, api.nvim_buf_line_count(0)) 2029 end) 2030 2031 describe(('replacing %d lines far before topline with 2 lines'):format(count), function() 2032 it('including final newline', function() 2033 api.nvim_buf_set_text(0, 0, 0, count, 0, { 'eee', 'fff', '' }) 2034 screen:expect_unchanged() 2035 eq(8 - count + 2, api.nvim_buf_line_count(0)) 2036 end) 2037 2038 it('excluding final newline', function() 2039 api.nvim_buf_set_text(0, 0, 0, count - 1, -1, { 'eee', 'fff' }) 2040 screen:expect_unchanged() 2041 eq(8 - count + 2, api.nvim_buf_line_count(0)) 2042 end) 2043 end) 2044 end 2045 2046 describe('replacing topline', function() 2047 describe('with 1 line', function() 2048 local s1 = [[ 2049 | 2050 {1:~ }|*4 2051 {2:[No Name] }| 2052 eee | 2053 xxx | 2054 ^yyy | 2055 zzz | 2056 {3:[No Name] [+] }| 2057 | 2058 ]] 2059 it('including final newline', function() 2060 api.nvim_buf_set_text(0, 4, 0, 5, 0, { 'eee', '' }) 2061 screen:expect(s1) 2062 end) 2063 it('excluding final newline', function() 2064 api.nvim_buf_set_text(0, 4, 0, 4, -1, { 'eee' }) 2065 screen:expect(s1) 2066 end) 2067 end) 2068 2069 describe('with 2 lines', function() 2070 local s2 = [[ 2071 | 2072 {1:~ }|*4 2073 {2:[No Name] }| 2074 eee | 2075 fff | 2076 xxx | 2077 ^yyy | 2078 {3:[No Name] [+] }| 2079 | 2080 ]] 2081 it('including final newline', function() 2082 api.nvim_buf_set_text(0, 4, 0, 5, 0, { 'eee', 'fff', '' }) 2083 screen:expect(s2) 2084 end) 2085 it('excluding final newline', function() 2086 api.nvim_buf_set_text(0, 4, 0, 4, -1, { 'eee', 'fff' }) 2087 screen:expect(s2) 2088 end) 2089 end) 2090 end) 2091 2092 it('inserting at start of topline', function() 2093 api.nvim_buf_set_text(0, 4, 0, 4, 0, { 'X', '' }) 2094 screen:expect([[ 2095 | 2096 {1:~ }|*4 2097 {2:[No Name] }| 2098 X | 2099 www | 2100 xxx | 2101 ^yyy | 2102 {3:[No Name] [+] }| 2103 | 2104 ]]) 2105 end) 2106 end) 2107 end) 2108 end) 2109 2110 describe_lua_and_rpc('nvim_buf_get_text', function(lua_or_rpc) 2111 local get_text = lua_or_rpc.nvim_buf_get_text 2112 before_each(function() 2113 insert([[ 2114 hello foo! 2115 text 2116 more]]) 2117 end) 2118 2119 it('works', function() 2120 eq({ 'hello' }, get_text(0, 0, 0, 0, 5, {})) 2121 eq({ 'hello foo!' }, get_text(0, 0, 0, 0, 42, {})) 2122 eq({ 'foo!' }, get_text(0, 0, 6, 0, 10, {})) 2123 eq({ 'foo!', 'tex' }, get_text(0, 0, 6, 1, 3, {})) 2124 eq({ 'foo!', 'tex' }, get_text(0, -3, 6, -2, 3, {})) 2125 eq({ '' }, get_text(0, 0, 18, 0, 20, {})) 2126 eq({ 'ext' }, get_text(0, -2, 1, -2, 4, {})) 2127 eq({ 'hello foo!', 'text', 'm' }, get_text(0, 0, 0, 2, 1, {})) 2128 eq({ 'hello foo!' }, get_text(0, 0, -987654321, 0, 987654321, {})) 2129 eq({ '' }, get_text(0, 0, -15, 0, -20, {})) 2130 end) 2131 2132 it('errors on out-of-range', function() 2133 eq('Index out of bounds', pcall_err(get_text, 0, 2, 0, 4, 0, {})) 2134 eq('Index out of bounds', pcall_err(get_text, 0, -4, 0, 0, 0, {})) 2135 eq('Index out of bounds', pcall_err(get_text, 0, 0, 0, 3, 0, {})) 2136 eq('Index out of bounds', pcall_err(get_text, 0, 0, 0, -4, 0, {})) 2137 -- no ml_get errors should happen #19017 2138 eq('', api.nvim_get_vvar('errmsg')) 2139 end) 2140 2141 it('errors when start is greater than end', function() 2142 eq("'start' is higher than 'end'", pcall_err(get_text, 0, 1, 0, 0, 0, {})) 2143 eq('start_col must be less than or equal to end_col', pcall_err(get_text, 0, 0, 1, 0, 0, {})) 2144 end) 2145 end) 2146 2147 describe('nvim_buf_get_offset', function() 2148 local get_offset = api.nvim_buf_get_offset 2149 it('works', function() 2150 api.nvim_buf_set_lines(0, 0, -1, true, { 'Some\r', 'exa\000mple', '', 'buf\rfer', 'text' }) 2151 eq(5, api.nvim_buf_line_count(0)) 2152 eq(0, get_offset(0, 0)) 2153 eq(6, get_offset(0, 1)) 2154 eq(15, get_offset(0, 2)) 2155 eq(16, get_offset(0, 3)) 2156 eq(24, get_offset(0, 4)) 2157 eq(29, get_offset(0, 5)) 2158 eq('Index out of bounds', pcall_err(get_offset, 0, 6)) 2159 eq('Index out of bounds', pcall_err(get_offset, 0, -1)) 2160 2161 api.nvim_set_option_value('eol', false, {}) 2162 api.nvim_set_option_value('fixeol', false, {}) 2163 eq(28, get_offset(0, 5)) 2164 2165 -- fileformat is ignored 2166 api.nvim_set_option_value('fileformat', 'dos', {}) 2167 eq(0, get_offset(0, 0)) 2168 eq(6, get_offset(0, 1)) 2169 eq(15, get_offset(0, 2)) 2170 eq(16, get_offset(0, 3)) 2171 eq(24, get_offset(0, 4)) 2172 eq(28, get_offset(0, 5)) 2173 api.nvim_set_option_value('eol', true, {}) 2174 eq(29, get_offset(0, 5)) 2175 2176 command('set hidden') 2177 command('enew') 2178 eq(6, api.nvim_buf_get_offset(1, 1)) 2179 command('bunload! 1') 2180 eq(-1, api.nvim_buf_get_offset(1, 1)) 2181 eq(-1, api.nvim_buf_get_offset(1, 0)) 2182 end) 2183 2184 it('works in empty buffer', function() 2185 eq(0, get_offset(0, 0)) 2186 eq(1, get_offset(0, 1)) 2187 eq(-1, fn.line2byte('$')) 2188 end) 2189 2190 it('works in buffer with one line inserted', function() 2191 feed('itext') 2192 eq(0, get_offset(0, 0)) 2193 eq(5, get_offset(0, 1)) 2194 end) 2195 end) 2196 2197 describe('nvim_buf_get_var, nvim_buf_set_var, nvim_buf_del_var', function() 2198 it('works', function() 2199 api.nvim_buf_set_var(0, 'lua', { 1, 2, { ['3'] = 1 } }) 2200 eq({ 1, 2, { ['3'] = 1 } }, api.nvim_buf_get_var(0, 'lua')) 2201 eq({ 1, 2, { ['3'] = 1 } }, api.nvim_eval('b:lua')) 2202 eq(1, fn.exists('b:lua')) 2203 api.nvim_buf_del_var(0, 'lua') 2204 eq(0, fn.exists('b:lua')) 2205 eq('Key not found: lua', pcall_err(api.nvim_buf_del_var, 0, 'lua')) 2206 api.nvim_buf_set_var(0, 'lua', 1) 2207 command('lockvar b:lua') 2208 eq('Key is locked: lua', pcall_err(api.nvim_buf_del_var, 0, 'lua')) 2209 eq('Key is locked: lua', pcall_err(api.nvim_buf_set_var, 0, 'lua', 1)) 2210 eq('Key is read-only: changedtick', pcall_err(api.nvim_buf_del_var, 0, 'changedtick')) 2211 eq('Key is read-only: changedtick', pcall_err(api.nvim_buf_set_var, 0, 'changedtick', 1)) 2212 end) 2213 end) 2214 2215 describe('nvim_buf_get_changedtick', function() 2216 it('works', function() 2217 eq(2, api.nvim_buf_get_changedtick(0)) 2218 api.nvim_buf_set_lines(0, 0, 1, false, { 'abc\0', '\0def', 'ghi' }) 2219 eq(3, api.nvim_buf_get_changedtick(0)) 2220 eq(3, api.nvim_buf_get_var(0, 'changedtick')) 2221 end) 2222 2223 it('buffer_set_var returns the old value', function() 2224 local val1 = { 1, 2, { ['3'] = 1 } } 2225 local val2 = { 4, 7 } 2226 eq(NIL, request('buffer_set_var', 0, 'lua', val1)) 2227 eq(val1, request('buffer_set_var', 0, 'lua', val2)) 2228 end) 2229 2230 it('buffer_del_var returns the old value', function() 2231 local val1 = { 1, 2, { ['3'] = 1 } } 2232 local val2 = { 4, 7 } 2233 eq(NIL, request('buffer_set_var', 0, 'lua', val1)) 2234 eq(val1, request('buffer_set_var', 0, 'lua', val2)) 2235 eq(val2, request('buffer_del_var', 0, 'lua')) 2236 end) 2237 end) 2238 2239 describe('nvim_get_option_value, nvim_set_option_value', function() 2240 it('works', function() 2241 eq(8, api.nvim_get_option_value('shiftwidth', {})) 2242 api.nvim_set_option_value('shiftwidth', 4, {}) 2243 eq(4, api.nvim_get_option_value('shiftwidth', {})) 2244 -- global-local option 2245 api.nvim_set_option_value('define', 'test', { buf = 0 }) 2246 eq('test', api.nvim_get_option_value('define', { buf = 0 })) 2247 -- Doesn't change the global value 2248 eq('', api.nvim_get_option_value('define', { scope = 'global' })) 2249 end) 2250 2251 it('returns values for unset local options', function() 2252 -- 'undolevels' is only set to its "unset" value when a new buffer is 2253 -- created 2254 command('enew') 2255 eq(-123456, api.nvim_get_option_value('undolevels', { buf = 0 })) 2256 end) 2257 end) 2258 2259 describe('nvim_buf_get_name, nvim_buf_set_name', function() 2260 it('works', function() 2261 command('new') 2262 eq('', api.nvim_buf_get_name(0)) 2263 local new_name = api.nvim_eval('resolve(tempname())') 2264 api.nvim_buf_set_name(0, new_name) 2265 eq(new_name, api.nvim_buf_get_name(0)) 2266 command('w!') 2267 eq(1, fn.filereadable(new_name)) 2268 os.remove(new_name) 2269 end) 2270 2271 describe("with 'autochdir'", function() 2272 local topdir 2273 local oldbuf 2274 local newbuf 2275 2276 before_each(function() 2277 command('set shellslash') 2278 topdir = fn.getcwd() 2279 t.mkdir(topdir .. '/Xacd') 2280 2281 oldbuf = api.nvim_get_current_buf() 2282 command('vnew') 2283 newbuf = api.nvim_get_current_buf() 2284 command('set autochdir') 2285 end) 2286 2287 after_each(function() 2288 n.rmdir(topdir .. '/Xacd') 2289 end) 2290 2291 it('does not change cwd with non-current buffer', function() 2292 api.nvim_buf_set_name(oldbuf, topdir .. '/Xacd/foo.txt') 2293 eq(topdir, fn.getcwd()) 2294 end) 2295 2296 it('changes cwd with current buffer', function() 2297 api.nvim_buf_set_name(newbuf, topdir .. '/Xacd/foo.txt') 2298 eq(topdir .. '/Xacd', fn.getcwd()) 2299 end) 2300 end) 2301 end) 2302 2303 describe('nvim_buf_is_loaded', function() 2304 it('works', function() 2305 -- record our buffer number for when we unload it 2306 local bufnr = api.nvim_get_current_buf() 2307 -- api should report that the buffer is loaded 2308 ok(api.nvim_buf_is_loaded(bufnr)) 2309 -- hide the current buffer by switching to a new empty buffer 2310 -- Careful! we need to modify the buffer first or vim will just reuse it 2311 api.nvim_buf_set_lines(bufnr, 0, -1, true, { 'line1' }) 2312 command('hide enew') 2313 -- confirm the buffer is hidden, but still loaded 2314 local infolist = api.nvim_eval('getbufinfo(' .. bufnr .. ')') 2315 eq(1, #infolist) 2316 eq(1, infolist[1].hidden) 2317 eq(1, infolist[1].loaded) 2318 -- now force unload the buffer 2319 command('bunload! ' .. bufnr) 2320 -- confirm the buffer is unloaded 2321 infolist = api.nvim_eval('getbufinfo(' .. bufnr .. ')') 2322 eq(0, infolist[1].loaded) 2323 -- nvim_buf_is_loaded() should also report the buffer as unloaded 2324 eq(false, api.nvim_buf_is_loaded(bufnr)) 2325 end) 2326 end) 2327 2328 describe('nvim_buf_is_valid', function() 2329 it('works', function() 2330 command('new') 2331 local b = api.nvim_get_current_buf() 2332 ok(api.nvim_buf_is_valid(b)) 2333 command('bw!') 2334 ok(not api.nvim_buf_is_valid(b)) 2335 end) 2336 end) 2337 2338 describe('nvim_buf_delete', function() 2339 it('allows for just deleting', function() 2340 command('new') 2341 local b = api.nvim_get_current_buf() 2342 ok(api.nvim_buf_is_valid(b)) 2343 api.nvim_buf_delete(b, {}) 2344 ok(not api.nvim_buf_is_loaded(b)) 2345 ok(not api.nvim_buf_is_valid(b)) 2346 end) 2347 2348 it('allows for just unloading', function() 2349 command('new') 2350 local b = api.nvim_get_current_buf() 2351 ok(api.nvim_buf_is_valid(b)) 2352 api.nvim_buf_delete(b, { unload = true }) 2353 ok(not api.nvim_buf_is_loaded(b)) 2354 ok(api.nvim_buf_is_valid(b)) 2355 end) 2356 end) 2357 2358 describe('nvim_buf_get_mark', function() 2359 it('works', function() 2360 api.nvim_buf_set_lines(0, -1, -1, true, { 'a', 'bit of', 'text' }) 2361 api.nvim_win_set_cursor(0, { 3, 4 }) 2362 command('mark v') 2363 eq({ 3, 0 }, api.nvim_buf_get_mark(0, 'v')) 2364 end) 2365 end) 2366 2367 describe('nvim_buf_set_mark', function() 2368 it('works with buffer local marks', function() 2369 api.nvim_buf_set_lines(0, -1, -1, true, { 'a', 'bit of', 'text' }) 2370 eq(true, api.nvim_buf_set_mark(0, 'z', 1, 1, {})) 2371 eq({ 1, 1 }, api.nvim_buf_get_mark(0, 'z')) 2372 eq({ 0, 1, 2, 0 }, fn.getpos("'z")) 2373 end) 2374 it('works with file/uppercase marks', function() 2375 api.nvim_buf_set_lines(0, -1, -1, true, { 'a', 'bit of', 'text' }) 2376 eq(true, api.nvim_buf_set_mark(0, 'Z', 3, 2, {})) 2377 eq({ 3, 2 }, api.nvim_buf_get_mark(0, 'Z')) 2378 eq({ api.nvim_get_current_buf(), 3, 3, 0 }, fn.getpos("'Z")) 2379 end) 2380 it('fails when invalid marks names are used', function() 2381 eq(false, pcall(api.nvim_buf_set_mark, 0, '!', 1, 0, {})) 2382 eq(false, pcall(api.nvim_buf_set_mark, 0, 'fail', 1, 0, {})) 2383 end) 2384 it('fails when invalid buffer number is used', function() 2385 eq(false, pcall(api.nvim_buf_set_mark, 99, 'a', 1, 1, {})) 2386 end) 2387 it('auto-loads unloaded buffer', function() 2388 local new_bufnr = fn.bufnr('set_mark', true) 2389 eq(false, api.nvim_buf_is_loaded(new_bufnr)) 2390 eq(true, api.nvim_buf_set_mark(new_bufnr, 'A', 0, 0, {})) 2391 eq(true, api.nvim_buf_is_loaded(new_bufnr)) 2392 eq({ 0, 0 }, api.nvim_buf_get_mark(new_bufnr, 'A')) 2393 end) 2394 end) 2395 2396 describe('nvim_buf_del_mark', function() 2397 it('works with buffer local marks', function() 2398 api.nvim_buf_set_lines(0, -1, -1, true, { 'a', 'bit of', 'text' }) 2399 api.nvim_buf_set_mark(0, 'z', 3, 1, {}) 2400 eq(true, api.nvim_buf_del_mark(0, 'z')) 2401 eq({ 0, 0 }, api.nvim_buf_get_mark(0, 'z')) 2402 end) 2403 it('works with file/uppercase marks', function() 2404 api.nvim_buf_set_lines(0, -1, -1, true, { 'a', 'bit of', 'text' }) 2405 api.nvim_buf_set_mark(0, 'Z', 3, 3, {}) 2406 eq(true, api.nvim_buf_del_mark(0, 'Z')) 2407 eq({ 0, 0 }, api.nvim_buf_get_mark(0, 'Z')) 2408 end) 2409 it('returns false in marks not set in this buffer', function() 2410 local abuf = api.nvim_create_buf(false, true) 2411 api.nvim_buf_set_lines(abuf, -1, -1, true, { 'a', 'bit of', 'text' }) 2412 api.nvim_buf_set_mark(abuf, 'A', 2, 2, {}) 2413 eq(false, api.nvim_buf_del_mark(0, 'A')) 2414 eq({ 2, 2 }, api.nvim_buf_get_mark(abuf, 'A')) 2415 end) 2416 it('returns false if mark was not deleted', function() 2417 api.nvim_buf_set_lines(0, -1, -1, true, { 'a', 'bit of', 'text' }) 2418 api.nvim_buf_set_mark(0, 'z', 3, 1, {}) 2419 eq(true, api.nvim_buf_del_mark(0, 'z')) 2420 eq(false, api.nvim_buf_del_mark(0, 'z')) -- Mark was already deleted 2421 end) 2422 it('fails when invalid marks names are used', function() 2423 eq(false, pcall(api.nvim_buf_del_mark, 0, '!')) 2424 eq(false, pcall(api.nvim_buf_del_mark, 0, 'fail')) 2425 end) 2426 it('fails when invalid buffer number is used', function() 2427 eq(false, pcall(api.nvim_buf_del_mark, 99, 'a')) 2428 end) 2429 end) 2430 end)