scrollback_spec.lua (47398B)
1 local t = require('test.testutil') 2 local n = require('test.functional.testnvim')() 3 local Screen = require('test.functional.ui.screen') 4 local tt = require('test.functional.testterm') 5 6 local clear, eq, neq = n.clear, t.eq, t.neq 7 local feed, testprg = n.feed, n.testprg 8 local fn = n.fn 9 local eval = n.eval 10 local command = n.command 11 local poke_eventloop = n.poke_eventloop 12 local retry = t.retry 13 local api = n.api 14 local feed_data = tt.feed_data 15 local pcall_err = t.pcall_err 16 local exec_lua = n.exec_lua 17 local assert_alive = n.assert_alive 18 local skip = t.skip 19 local is_os = t.is_os 20 21 local function test_terminal_scrollback(hide_curbuf) 22 local screen --- @type test.functional.ui.screen 23 local buf --- @type integer 24 local chan --- @type integer 25 local otherbuf --- @type integer 26 local restore_terminal_mode --- @type boolean? 27 local save_feed_data = feed_data 28 29 local function may_hide_curbuf() 30 if hide_curbuf then 31 eq(nil, restore_terminal_mode) 32 restore_terminal_mode = vim.startswith(api.nvim_get_mode().mode, 't') 33 api.nvim_set_current_buf(otherbuf) 34 end 35 end 36 37 local function may_restore_curbuf() 38 if hide_curbuf then 39 neq(nil, restore_terminal_mode) 40 eq(buf, fn.bufnr('#')) 41 feed('<C-^>') -- "view" in 'jumpoptions' applies to this 42 if restore_terminal_mode then 43 feed('i') 44 else 45 -- Cursor position was restored from wi_mark, not b_last_cursor. 46 -- Check that b_last_cursor and wi_mark are the same. 47 --- @type integer[], integer[] 48 local last_cursor, restored_cursor = unpack(exec_lua(function() 49 -- Get these two positions on the same RPC call. 50 return { vim.fn.getpos([['"]]), vim.fn.getpos('.') } 51 end)) 52 if last_cursor[2] > 0 then 53 eq(restored_cursor, last_cursor) 54 else 55 eq({ 0, 0, 0, 0 }, last_cursor) 56 eq({ 0, 1, 1, 0 }, restored_cursor) 57 end 58 end 59 restore_terminal_mode = nil 60 end 61 end 62 63 setup(function() 64 feed_data = function(data) 65 may_hide_curbuf() 66 api.nvim_chan_send(chan, data) 67 may_restore_curbuf() 68 end 69 end) 70 71 teardown(function() 72 feed_data = save_feed_data 73 end) 74 75 --- @param prefix string 76 --- @param start integer 77 --- @param stop integer 78 local function feed_lines(prefix, start, stop) 79 may_hide_curbuf() 80 local data = '' 81 for i = start, stop do 82 data = data .. prefix .. tostring(i) .. '\n' 83 end 84 api.nvim_chan_send(chan, data) 85 retry(nil, 1000, function() 86 eq({ prefix .. tostring(stop), '' }, api.nvim_buf_get_lines(buf, -3, -1, true)) 87 end) 88 may_restore_curbuf() 89 end 90 91 local function try_resize(width, height) 92 may_hide_curbuf() 93 screen:try_resize(width, height) 94 may_restore_curbuf() 95 end 96 97 before_each(function() 98 clear() 99 command('set nostartofline jumpoptions+=view') 100 screen = tt.setup_screen(nil, nil, 30) 101 buf = api.nvim_get_current_buf() 102 chan = api.nvim_get_option_value('channel', { buf = buf }) 103 otherbuf = hide_curbuf and api.nvim_create_buf(true, false) or nil 104 restore_terminal_mode = nil 105 end) 106 107 describe('when the limit is exceeded', function() 108 before_each(function() 109 feed_lines('line', 1, 30) 110 screen:expect([[ 111 line26 | 112 line27 | 113 line28 | 114 line29 | 115 line30 | 116 ^ | 117 {5:-- TERMINAL --} | 118 ]]) 119 eq(16, api.nvim_buf_line_count(0)) 120 end) 121 122 it('will delete extra lines at the top', function() 123 feed('<c-\\><c-n>gg') 124 screen:expect([[ 125 ^line16 | 126 line17 | 127 line18 | 128 line19 | 129 line20 | 130 line21 | 131 | 132 ]]) 133 end) 134 135 describe('and cursor on non-last row in screen', function() 136 before_each(function() 137 feed([[<C-\><C-N>M$]]) 138 fn.setpos("'m", { 0, 13, 4, 0 }) 139 local ns = api.nvim_create_namespace('test') 140 api.nvim_buf_set_extmark(0, ns, 12, 0, { end_col = 6, hl_group = 'ErrorMsg' }) 141 screen:expect([[ 142 line26 | 143 line27 | 144 {101:line2^8} | 145 line29 | 146 line30 | 147 |*2 148 ]]) 149 end) 150 151 it("outputting fewer than 'scrollback' lines", function() 152 feed_lines('new_line', 1, 6) 153 screen:expect([[ 154 line26 | 155 line27 | 156 {101:line2^8} | 157 line29 | 158 line30 | 159 new_line1 | 160 | 161 ]]) 162 eq({ 0, 7, 4, 0 }, fn.getpos("'m")) 163 eq({ 0, 7, 6, 0 }, fn.getpos('.')) 164 end) 165 166 it("outputting more than 'scrollback' lines", function() 167 feed_lines('new_line', 1, 11) 168 screen:expect([[ 169 line27 | 170 {101:line2^8} | 171 line29 | 172 line30 | 173 new_line1 | 174 new_line2 | 175 | 176 ]]) 177 eq({ 0, 2, 4, 0 }, fn.getpos("'m")) 178 eq({ 0, 2, 6, 0 }, fn.getpos('.')) 179 end) 180 181 it('outputting more lines than whole buffer', function() 182 feed_lines('new_line', 1, 20) 183 screen:expect([[ 184 ^new_line6 | 185 new_line7 | 186 new_line8 | 187 new_line9 | 188 new_line10 | 189 new_line11 | 190 | 191 ]]) 192 eq({ 0, 0, 0, 0 }, fn.getpos("'m")) 193 eq({ 0, 1, 1, 0 }, fn.getpos('.')) 194 end) 195 196 it('clearing scrollback with ED 3', function() 197 feed_data('\027[3J') 198 screen:expect_unchanged(hide_curbuf) 199 eq({ 0, 3, 4, 0 }, fn.getpos("'m")) 200 eq({ 0, 3, 6, 0 }, fn.getpos('.')) 201 feed('gg') 202 screen:expect([[ 203 line2^6 | 204 line27 | 205 {101:line28} | 206 line29 | 207 line30 | 208 |*2 209 ]]) 210 end) 211 212 it('clearing scrollback with ED 3 and outputting lines', function() 213 feed_data('\027[3J' .. 'new_line1\nnew_line2\nnew_line3') 214 screen:expect([[ 215 line26 | 216 line27 | 217 {101:line2^8} | 218 line29 | 219 line30 | 220 new_line1 | 221 | 222 ]]) 223 eq({ 0, 3, 4, 0 }, fn.getpos("'m")) 224 eq({ 0, 3, 6, 0 }, fn.getpos('.')) 225 end) 226 227 it('clearing scrollback with ED 3 between outputting lines', function() 228 skip(is_os('win'), 'FIXME: wrong behavior on Windows, ConPTY bug?') 229 feed_data('line31\nline32\n' .. '\027[3J' .. 'new_line1\nnew_line2') 230 screen:expect([[ 231 {101:line2^8} | 232 line29 | 233 line30 | 234 line31 | 235 line32 | 236 new_line1 | 237 | 238 ]]) 239 eq({ 0, 1, 4, 0 }, fn.getpos("'m")) 240 eq({ 0, 1, 6, 0 }, fn.getpos('.')) 241 end) 242 end) 243 244 describe('and cursor on scrollback row #12651', function() 245 before_each(function() 246 feed([[<C-\><C-N>Hk$]]) 247 fn.setpos("'m", { 0, 10, 4, 0 }) 248 local ns = api.nvim_create_namespace('test') 249 api.nvim_buf_set_extmark(0, ns, 9, 0, { end_col = 6, hl_group = 'ErrorMsg' }) 250 screen:expect([[ 251 {101:line2^5} | 252 line26 | 253 line27 | 254 line28 | 255 line29 | 256 line30 | 257 | 258 ]]) 259 end) 260 261 it("outputting fewer than 'scrollback' lines", function() 262 feed_lines('new_line', 1, 6) 263 screen:expect_unchanged(hide_curbuf) 264 eq({ 0, 4, 4, 0 }, fn.getpos("'m")) 265 eq({ 0, 4, 6, 0 }, fn.getpos('.')) 266 end) 267 268 it("outputting more than 'scrollback' lines", function() 269 feed_lines('new_line', 1, 11) 270 screen:expect([[ 271 ^line27 | 272 line28 | 273 line29 | 274 line30 | 275 new_line1 | 276 new_line2 | 277 | 278 ]]) 279 eq({ 0, 0, 0, 0 }, fn.getpos("'m")) 280 eq({ 0, 1, 1, 0 }, fn.getpos('.')) 281 end) 282 end) 283 284 it('changing window height does not duplicate lines', function() 285 -- XXX: Can't test this reliably on Windows unless the cursor is _moved_ 286 -- by the resize. http://docs.libuv.org/en/v1.x/signal.html 287 -- See also: https://github.com/rprichard/winpty/issues/110 288 skip(is_os('win')) 289 try_resize(screen._width, screen._height + 4) 290 screen:expect([[ 291 line23 | 292 line24 | 293 line25 | 294 line26 | 295 line27 | 296 line28 | 297 line29 | 298 line30 | 299 rows: 10, cols: 30 | 300 ^ | 301 {5:-- TERMINAL --} | 302 ]]) 303 eq(17, api.nvim_buf_line_count(0)) 304 try_resize(screen._width, screen._height - 2) 305 screen:expect([[ 306 line26 | 307 line27 | 308 line28 | 309 line29 | 310 line30 | 311 rows: 10, cols: 30 | 312 rows: 8, cols: 30 | 313 ^ | 314 {5:-- TERMINAL --} | 315 ]]) 316 eq(18, api.nvim_buf_line_count(0)) 317 try_resize(screen._width, screen._height - 3) 318 screen:expect([[ 319 line30 | 320 rows: 10, cols: 30 | 321 rows: 8, cols: 30 | 322 rows: 5, cols: 30 | 323 ^ | 324 {5:-- TERMINAL --} | 325 ]]) 326 eq(15, api.nvim_buf_line_count(0)) 327 try_resize(screen._width, screen._height + 3) 328 screen:expect([[ 329 line28 | 330 line29 | 331 line30 | 332 rows: 10, cols: 30 | 333 rows: 8, cols: 30 | 334 rows: 5, cols: 30 | 335 rows: 8, cols: 30 | 336 ^ | 337 {5:-- TERMINAL --} | 338 ]]) 339 eq(16, api.nvim_buf_line_count(0)) 340 feed([[<C-\><C-N>8<C-Y>]]) 341 screen:expect([[ 342 line20 | 343 line21 | 344 line22 | 345 line23 | 346 line24 | 347 line25 | 348 line26 | 349 ^line27 | 350 | 351 ]]) 352 end) 353 end) 354 355 describe('with cursor at last row', function() 356 before_each(function() 357 feed_lines('line', 1, 4) 358 screen:expect([[ 359 tty ready | 360 line1 | 361 line2 | 362 line3 | 363 line4 | 364 ^ | 365 {5:-- TERMINAL --} | 366 ]]) 367 fn.setpos("'m", { 0, 3, 4, 0 }) 368 local ns = api.nvim_create_namespace('test') 369 api.nvim_buf_set_extmark(0, ns, 2, 0, { end_col = 5, hl_group = 'ErrorMsg' }) 370 screen:expect([[ 371 tty ready | 372 line1 | 373 {101:line2} | 374 line3 | 375 line4 | 376 ^ | 377 {5:-- TERMINAL --} | 378 ]]) 379 end) 380 381 it("outputting more than 'scrollback' lines in Normal mode", function() 382 feed([[<C-\><C-N>]]) 383 feed_lines('new_line', 1, 11) 384 screen:expect([[ 385 new_line7 | 386 new_line8 | 387 new_line9 | 388 new_line10 | 389 new_line11 | 390 ^ | 391 | 392 ]]) 393 feed('gg') 394 screen:expect([[ 395 ^line1 | 396 {101:line2} | 397 line3 | 398 line4 | 399 new_line1 | 400 new_line2 | 401 | 402 ]]) 403 eq({ 0, 2, 4, 0 }, fn.getpos("'m")) 404 feed('G') 405 feed_lines('new_line', 12, 31) 406 screen:expect([[ 407 new_line27 | 408 new_line28 | 409 new_line29 | 410 new_line30 | 411 new_line31 | 412 ^ | 413 | 414 ]]) 415 feed('gg') 416 screen:expect([[ 417 ^new_line17 | 418 new_line18 | 419 new_line19 | 420 new_line20 | 421 new_line21 | 422 new_line22 | 423 | 424 ]]) 425 eq({ 0, 0, 0, 0 }, fn.getpos("'m")) 426 end) 427 428 it('clearing scrollback with ED 3', function() 429 -- Clearing empty scrollback and then outputting a line 430 feed_data('\027[3J' .. 'line5\n') 431 screen:expect([[ 432 line1 | 433 {101:line2} | 434 line3 | 435 line4 | 436 line5 | 437 ^ | 438 {5:-- TERMINAL --} | 439 ]]) 440 eq(7, api.nvim_buf_line_count(0)) 441 eq({ 0, 3, 4, 0 }, fn.getpos("'m")) 442 -- Clearing 1 line of scrollback 443 feed_data('\027[3J') 444 screen:expect_unchanged(hide_curbuf) 445 eq(6, api.nvim_buf_line_count(0)) 446 eq({ 0, 2, 4, 0 }, fn.getpos("'m")) 447 -- Outputting a line 448 feed_data('line6\n') 449 screen:expect([[ 450 {101:line2} | 451 line3 | 452 line4 | 453 line5 | 454 line6 | 455 ^ | 456 {5:-- TERMINAL --} | 457 ]]) 458 eq(7, api.nvim_buf_line_count(0)) 459 eq({ 0, 2, 4, 0 }, fn.getpos("'m")) 460 -- Clearing 1 line of scrollback and then outputting a line 461 feed_data('\027[3J' .. 'line7\n') 462 screen:expect([[ 463 line3 | 464 line4 | 465 line5 | 466 line6 | 467 line7 | 468 ^ | 469 {5:-- TERMINAL --} | 470 ]]) 471 eq(7, api.nvim_buf_line_count(0)) 472 eq({ 0, 1, 4, 0 }, fn.getpos("'m")) 473 -- Check first line of buffer in Normal mode 474 feed([[<C-\><C-N>gg]]) 475 screen:expect([[ 476 {101:^line2} | 477 line3 | 478 line4 | 479 line5 | 480 line6 | 481 line7 | 482 | 483 ]]) 484 feed('G') 485 -- Outputting lines and then clearing scrollback 486 skip(is_os('win'), 'FIXME: wrong behavior on Windows, ConPTY bug?') 487 feed_data('line8\nline9\n' .. '\027[3J') 488 screen:expect([[ 489 line5 | 490 line6 | 491 line7 | 492 line8 | 493 line9 | 494 ^ | 495 | 496 ]]) 497 eq(6, api.nvim_buf_line_count(0)) 498 eq({ 0, 0, 0, 0 }, fn.getpos("'m")) 499 end) 500 501 describe('and 1 line is printed', function() 502 before_each(function() 503 feed_lines('line', 5, 5) 504 end) 505 506 it('will hide the top line', function() 507 screen:expect([[ 508 line1 | 509 {101:line2} | 510 line3 | 511 line4 | 512 line5 | 513 ^ | 514 {5:-- TERMINAL --} | 515 ]]) 516 eq(7, api.nvim_buf_line_count(0)) 517 eq({ 0, 3, 4, 0 }, fn.getpos("'m")) 518 end) 519 520 describe('and then 3 more lines are printed', function() 521 before_each(function() 522 feed_lines('line', 6, 8) 523 end) 524 525 it('will hide the top 4 lines', function() 526 screen:expect([[ 527 line4 | 528 line5 | 529 line6 | 530 line7 | 531 line8 | 532 ^ | 533 {5:-- TERMINAL --} | 534 ]]) 535 eq({ 0, 3, 4, 0 }, fn.getpos("'m")) 536 537 feed('<c-\\><c-n>6k') 538 screen:expect([[ 539 ^line3 | 540 line4 | 541 line5 | 542 line6 | 543 line7 | 544 line8 | 545 | 546 ]]) 547 548 feed('gg') 549 screen:expect([[ 550 ^tty ready | 551 line1 | 552 {101:line2} | 553 line3 | 554 line4 | 555 line5 | 556 | 557 ]]) 558 559 feed('G') 560 screen:expect([[ 561 line4 | 562 line5 | 563 line6 | 564 line7 | 565 line8 | 566 ^ | 567 | 568 ]]) 569 end) 570 end) 571 end) 572 573 describe('and height decreased by 1', function() 574 local function will_hide_top_line() 575 feed([[<C-\><C-N>]]) 576 try_resize(screen._width - 2, screen._height - 1) 577 screen:expect([[ 578 {101:line2} | 579 line3 | 580 line4 | 581 rows: 5, cols: 28 | 582 ^ | 583 | 584 ]]) 585 eq({ 0, 3, 4, 0 }, fn.getpos("'m")) 586 end 587 588 it('will hide top line', will_hide_top_line) 589 590 describe('and then decreased by 2', function() 591 before_each(function() 592 will_hide_top_line() 593 try_resize(screen._width - 2, screen._height - 2) 594 end) 595 596 it('will hide the top 3 lines', function() 597 screen:expect([[ 598 rows: 5, cols: 28 | 599 rows: 3, cols: 26 | 600 ^ | 601 | 602 ]]) 603 eq(8, api.nvim_buf_line_count(0)) 604 eq({ 0, 3, 4, 0 }, fn.getpos("'m")) 605 feed('3k') 606 screen:expect([[ 607 ^line4 | 608 rows: 5, cols: 28 | 609 rows: 3, cols: 26 | 610 | 611 ]]) 612 feed('gg') 613 screen:expect([[ 614 ^tty ready | 615 line1 | 616 {101:line2} | 617 | 618 ]]) 619 end) 620 end) 621 end) 622 end) 623 624 describe('with empty lines after the cursor', function() 625 -- XXX: Can't test this reliably on Windows unless the cursor is _moved_ 626 -- by the resize. http://docs.libuv.org/en/v1.x/signal.html 627 -- See also: https://github.com/rprichard/winpty/issues/110 628 if skip(is_os('win')) then 629 return 630 end 631 632 describe('and the height is decreased by 2', function() 633 before_each(function() 634 try_resize(screen._width, screen._height - 2) 635 end) 636 637 local function will_delete_last_two_lines() 638 screen:expect([[ 639 tty ready | 640 rows: 4, cols: 30 | 641 ^ | 642 | 643 {5:-- TERMINAL --} | 644 ]]) 645 eq(4, api.nvim_buf_line_count(0)) 646 end 647 648 it('will delete the last two empty lines', will_delete_last_two_lines) 649 650 describe('and then decreased by 1', function() 651 before_each(function() 652 will_delete_last_two_lines() 653 try_resize(screen._width, screen._height - 1) 654 end) 655 656 it('will delete the last line and hide the first', function() 657 screen:expect([[ 658 rows: 4, cols: 30 | 659 rows: 3, cols: 30 | 660 ^ | 661 {5:-- TERMINAL --} | 662 ]]) 663 eq(4, api.nvim_buf_line_count(0)) 664 feed('<c-\\><c-n>gg') 665 screen:expect([[ 666 ^tty ready | 667 rows: 4, cols: 30 | 668 rows: 3, cols: 30 | 669 | 670 ]]) 671 feed('a') 672 screen:expect([[ 673 rows: 4, cols: 30 | 674 rows: 3, cols: 30 | 675 ^ | 676 {5:-- TERMINAL --} | 677 ]]) 678 end) 679 end) 680 end) 681 end) 682 683 describe('with 4 lines hidden in the scrollback', function() 684 before_each(function() 685 feed_lines('line', 1, 4) 686 screen:expect([[ 687 tty ready | 688 line1 | 689 line2 | 690 line3 | 691 line4 | 692 ^ | 693 {5:-- TERMINAL --} | 694 ]]) 695 fn.setpos("'m", { 0, 3, 4, 0 }) 696 local ns = api.nvim_create_namespace('test') 697 api.nvim_buf_set_extmark(0, ns, 2, 0, { end_col = 5, hl_group = 'ErrorMsg' }) 698 screen:expect([[ 699 tty ready | 700 line1 | 701 {101:line2} | 702 line3 | 703 line4 | 704 ^ | 705 {5:-- TERMINAL --} | 706 ]]) 707 try_resize(screen._width, screen._height - 3) 708 screen:expect([[ 709 line4 | 710 rows: 3, cols: 30 | 711 ^ | 712 {5:-- TERMINAL --} | 713 ]]) 714 eq(7, api.nvim_buf_line_count(0)) 715 end) 716 717 describe('and the height is increased by 1', function() 718 -- XXX: Can't test this reliably on Windows unless the cursor is _moved_ 719 -- by the resize. http://docs.libuv.org/en/v1.x/signal.html 720 -- See also: https://github.com/rprichard/winpty/issues/110 721 if skip(is_os('win')) then 722 return 723 end 724 local function pop_then_push() 725 try_resize(screen._width, screen._height + 1) 726 screen:expect([[ 727 line4 | 728 rows: 3, cols: 30 | 729 rows: 4, cols: 30 | 730 ^ | 731 {5:-- TERMINAL --} | 732 ]]) 733 eq({ 0, 3, 4, 0 }, fn.getpos("'m")) 734 end 735 736 it('will pop 1 line and then push it back', pop_then_push) 737 738 describe('and then by 3', function() 739 before_each(function() 740 pop_then_push() 741 eq(8, api.nvim_buf_line_count(0)) 742 try_resize(screen._width, screen._height + 3) 743 end) 744 745 local function pop3_then_push1() 746 screen:expect([[ 747 {101:line2} | 748 line3 | 749 line4 | 750 rows: 3, cols: 30 | 751 rows: 4, cols: 30 | 752 rows: 7, cols: 30 | 753 ^ | 754 {5:-- TERMINAL --} | 755 ]]) 756 eq(9, api.nvim_buf_line_count(0)) 757 eq({ 0, 3, 4, 0 }, fn.getpos("'m")) 758 feed('<c-\\><c-n>gg') 759 screen:expect([[ 760 ^tty ready | 761 line1 | 762 {101:line2} | 763 line3 | 764 line4 | 765 rows: 3, cols: 30 | 766 rows: 4, cols: 30 | 767 | 768 ]]) 769 end 770 771 it('will pop 3 lines and then push one back', pop3_then_push1) 772 773 describe('and then by 4', function() 774 before_each(function() 775 pop3_then_push1() 776 feed('Gi') 777 try_resize(screen._width, screen._height + 4) 778 end) 779 780 it('will show all lines and leave a blank one at the end', function() 781 screen:expect([[ 782 tty ready | 783 line1 | 784 {101:line2} | 785 line3 | 786 line4 | 787 rows: 3, cols: 30 | 788 rows: 4, cols: 30 | 789 rows: 7, cols: 30 | 790 rows: 11, cols: 30 | 791 ^ | 792 | 793 {5:-- TERMINAL --} | 794 ]]) 795 -- since there's an empty line after the cursor, the buffer line 796 -- count equals the terminal screen height 797 eq(11, api.nvim_buf_line_count(0)) 798 eq({ 0, 3, 4, 0 }, fn.getpos("'m")) 799 end) 800 end) 801 end) 802 end) 803 end) 804 805 it('reducing &scrollback deletes extra lines immediately', function() 806 feed_lines('line', 1, 30) 807 screen:expect([[ 808 line26 | 809 line27 | 810 line28 | 811 line29 | 812 line30 | 813 ^ | 814 {5:-- TERMINAL --} | 815 ]]) 816 local term_height = 6 -- Actual terminal screen height, not the scrollback 817 -- Initial 818 local scrollback = api.nvim_get_option_value('scrollback', { buf = buf }) 819 eq(scrollback + term_height, fn.line('$')) 820 eq(scrollback + term_height, fn.line('.')) 821 n.fn.setpos("'m", { 0, scrollback + 1, 4, 0 }) 822 local ns = api.nvim_create_namespace('test') 823 api.nvim_buf_set_extmark(0, ns, scrollback, 0, { end_col = 6, hl_group = 'ErrorMsg' }) 824 screen:expect([[ 825 {101:line26} | 826 line27 | 827 line28 | 828 line29 | 829 line30 | 830 ^ | 831 {5:-- TERMINAL --} | 832 ]]) 833 -- Reduction 834 scrollback = scrollback - 2 835 may_hide_curbuf() 836 api.nvim_set_option_value('scrollback', scrollback, { buf = buf }) 837 may_restore_curbuf() 838 eq(scrollback + term_height, fn.line('$')) 839 eq(scrollback + term_height, fn.line('.')) 840 screen:expect_unchanged(hide_curbuf) 841 eq({ 0, scrollback + 1, 4, 0 }, n.fn.getpos("'m")) 842 end) 843 end 844 845 describe(':terminal scrollback', function() 846 describe('in current buffer', function() 847 test_terminal_scrollback(false) 848 end) 849 850 describe('in hidden buffer', function() 851 test_terminal_scrollback(true) 852 end) 853 end) 854 855 describe(':terminal prints more lines than the screen height and exits', function() 856 it('will push extra lines to scrollback', function() 857 clear() 858 local screen = Screen.new(30, 7, { rgb = false }) 859 screen:add_extra_attr_ids({ [100] = { foreground = 12 } }) 860 command( 861 ("call jobstart(['%s', '10'], {'term':v:true}) | startinsert"):format(testprg('tty-test')) 862 ) 863 screen:expect([[ 864 line6 | 865 line7 | 866 line8 | 867 line9 | 868 | 869 [Process exited 0]^ | 870 {5:-- TERMINAL --} | 871 ]]) 872 feed('<cr>') 873 -- closes the buffer correctly after pressing a key 874 screen:expect([[ 875 ^ | 876 {100:~ }|*5 877 | 878 ]]) 879 end) 880 end) 881 882 describe("'scrollback' option", function() 883 before_each(function() 884 clear() 885 end) 886 887 local function set_fake_shell() 888 api.nvim_set_option_value('shell', string.format('"%s" INTERACT', testprg('shell-test')), {}) 889 end 890 891 local function expect_lines(expected, epsilon) 892 local ep = epsilon and epsilon or 0 893 local actual = eval("line('$')") 894 if expected > actual + ep and expected < actual - ep then 895 error('expected (+/- ' .. ep .. '): ' .. expected .. ', actual: ' .. tostring(actual)) 896 end 897 end 898 899 it('set to 0 behaves as 1', function() 900 local screen 901 if is_os('win') then 902 screen = tt.setup_screen(nil, { 'cmd.exe' }, 30) 903 else 904 screen = tt.setup_screen(nil, { 'sh' }, 30) 905 end 906 907 api.nvim_set_option_value('scrollback', 0, {}) 908 feed_data(('%s REP 31 line%s'):format(testprg('shell-test'), is_os('win') and '\r' or '\n')) 909 screen:expect { any = '30: line ' } 910 retry(nil, nil, function() 911 expect_lines(7) 912 end) 913 end) 914 915 it('deletes lines (only) if necessary', function() 916 local screen 917 if is_os('win') then 918 command([[let $PROMPT='$$']]) 919 screen = tt.setup_screen(nil, { 'cmd.exe' }, 30) 920 else 921 command('let $PS1 = "$"') 922 screen = tt.setup_screen(nil, { 'sh' }, 30) 923 end 924 925 api.nvim_set_option_value('scrollback', 200, {}) 926 927 -- Wait for prompt. 928 screen:expect { any = '%$' } 929 930 feed_data(('%s REP 31 line%s'):format(testprg('shell-test'), is_os('win') and '\r' or '\n')) 931 screen:expect { any = '30: line ' } 932 933 retry(nil, nil, function() 934 expect_lines(33, 2) 935 end) 936 api.nvim_set_option_value('scrollback', 10, {}) 937 poke_eventloop() 938 retry(nil, nil, function() 939 expect_lines(16) 940 end) 941 api.nvim_set_option_value('scrollback', 10000, {}) 942 retry(nil, nil, function() 943 expect_lines(16) 944 end) 945 -- Terminal job data is received asynchronously, may happen before the 946 -- 'scrollback' option is synchronized with the internal sb_buffer. 947 command('sleep 100m') 948 949 feed_data(('%s REP 41 line%s'):format(testprg('shell-test'), is_os('win') and '\r' or '\n')) 950 if is_os('win') then 951 screen:expect([[ 952 37: line | 953 38: line | 954 39: line | 955 40: line | 956 | 957 $^ | 958 {5:-- TERMINAL --} | 959 ]]) 960 else 961 screen:expect([[ 962 36: line | 963 37: line | 964 38: line | 965 39: line | 966 40: line | 967 {MATCH:.*}| 968 {5:-- TERMINAL --} | 969 ]]) 970 end 971 expect_lines(58) 972 973 -- Verify off-screen state 974 eq((is_os('win') and '36: line' or '35: line'), eval("getline(line('w0') - 1)->trim(' ', 2)")) 975 eq((is_os('win') and '27: line' or '26: line'), eval("getline(line('w0') - 10)->trim(' ', 2)")) 976 end) 977 978 it('defaults to 10000 in :terminal buffers', function() 979 set_fake_shell() 980 command('terminal') 981 eq(10000, api.nvim_get_option_value('scrollback', {})) 982 end) 983 984 it('error if set to invalid value', function() 985 eq('Vim(set):E474: Invalid argument: scrollback=-2', pcall_err(command, 'set scrollback=-2')) 986 eq( 987 'Vim(set):E474: Invalid argument: scrollback=1000001', 988 pcall_err(command, 'set scrollback=1000001') 989 ) 990 end) 991 992 it('defaults to -1 on normal buffers', function() 993 command('new') 994 eq(-1, api.nvim_get_option_value('scrollback', {})) 995 end) 996 997 it(':setlocal in a :terminal buffer', function() 998 set_fake_shell() 999 1000 -- _Global_ scrollback=-1 defaults :terminal to 10_000. 1001 command('setglobal scrollback=-1') 1002 command('terminal') 1003 eq(10000, api.nvim_get_option_value('scrollback', {})) 1004 1005 -- _Local_ scrollback=-1 in :terminal forces the _maximum_. 1006 command('setlocal scrollback=-1') 1007 retry(nil, nil, function() -- Fixup happens on refresh, not immediately. 1008 eq(1000000, api.nvim_get_option_value('scrollback', {})) 1009 end) 1010 1011 -- _Local_ scrollback=-1 during TermOpen forces the maximum. #9605 1012 command('setglobal scrollback=-1') 1013 command('autocmd TermOpen * setlocal scrollback=-1') 1014 command('terminal') 1015 eq(1000000, api.nvim_get_option_value('scrollback', {})) 1016 end) 1017 1018 it(':setlocal in a normal buffer', function() 1019 command('new') 1020 -- :setlocal to -1. 1021 command('setlocal scrollback=-1') 1022 eq(-1, api.nvim_get_option_value('scrollback', {})) 1023 -- :setlocal to anything except -1. Currently, this just has no effect. 1024 command('setlocal scrollback=42') 1025 eq(42, api.nvim_get_option_value('scrollback', {})) 1026 end) 1027 1028 it(':set updates local value and global default', function() 1029 set_fake_shell() 1030 command('set scrollback=42') -- set global value 1031 eq(42, api.nvim_get_option_value('scrollback', {})) 1032 command('terminal') 1033 eq(42, api.nvim_get_option_value('scrollback', {})) -- inherits global default 1034 command('setlocal scrollback=99') 1035 eq(99, api.nvim_get_option_value('scrollback', {})) 1036 command('set scrollback<') -- reset to global default 1037 eq(42, api.nvim_get_option_value('scrollback', {})) 1038 command('setglobal scrollback=734') -- new global default 1039 eq(42, api.nvim_get_option_value('scrollback', {})) -- local value did not change 1040 command('terminal') 1041 eq(734, api.nvim_get_option_value('scrollback', {})) 1042 end) 1043 end) 1044 1045 describe('pending scrollback line handling', function() 1046 local screen 1047 1048 before_each(function() 1049 clear() 1050 screen = Screen.new(30, 7) 1051 end) 1052 1053 it("does not crash after setting 'number' #14891", function() 1054 exec_lua [[ 1055 local api = vim.api 1056 local buf = api.nvim_create_buf(true, true) 1057 local chan = api.nvim_open_term(buf, {}) 1058 vim.wo.number = true 1059 api.nvim_chan_send(chan, ("a\n"):rep(11) .. "a") 1060 api.nvim_win_set_buf(0, buf) 1061 ]] 1062 screen:expect [[ 1063 {8: 1 }^a | 1064 {8: 2 }a | 1065 {8: 3 }a | 1066 {8: 4 }a | 1067 {8: 5 }a | 1068 {8: 6 }a | 1069 | 1070 ]] 1071 feed('G') 1072 screen:expect [[ 1073 {8: 7 }a | 1074 {8: 8 }a | 1075 {8: 9 }a | 1076 {8: 10 }a | 1077 {8: 11 }a | 1078 {8: 12 }^a | 1079 | 1080 ]] 1081 assert_alive() 1082 end) 1083 1084 it('does not crash after nvim_buf_call #14891', function() 1085 exec_lua( 1086 [[ 1087 local bufnr = vim.api.nvim_create_buf(false, true) 1088 local args = ... 1089 vim.api.nvim_buf_call(bufnr, function() 1090 vim.fn.jobstart(args, { term = true }) 1091 end) 1092 vim.api.nvim_win_set_buf(0, bufnr) 1093 vim.cmd('startinsert') 1094 ]], 1095 is_os('win') and { 'cmd.exe', '/c', 'for /L %I in (1,1,12) do @echo hi' } 1096 or { 'printf', ('hi\n'):rep(12) } 1097 ) 1098 screen:expect [[ 1099 hi |*4 1100 | 1101 [Process exited 0]^ | 1102 {5:-- TERMINAL --} | 1103 ]] 1104 assert_alive() 1105 end) 1106 1107 it('does not crash after deleting buffer lines', function() 1108 local buf = api.nvim_get_current_buf() 1109 local chan = api.nvim_open_term(buf, {}) 1110 api.nvim_chan_send(chan, ('a\n'):rep(11) .. 'a') 1111 screen:expect([[ 1112 ^a | 1113 a |*5 1114 | 1115 ]]) 1116 api.nvim_set_option_value('modifiable', true, { buf = buf }) 1117 api.nvim_buf_set_lines(buf, 0, -1, true, {}) 1118 screen:expect([[ 1119 ^ | 1120 {1:~ }|*5 1121 | 1122 ]]) 1123 api.nvim_chan_send(chan, ('\nb'):rep(11) .. '\n') 1124 screen:expect([[ 1125 b |*5 1126 ^ | 1127 | 1128 ]]) 1129 assert_alive() 1130 end) 1131 end) 1132 1133 describe('scrollback is correct', function() 1134 local screen --- @type test.functional.ui.screen 1135 local buf --- @type integer 1136 local win --- @type integer 1137 1138 before_each(function() 1139 clear() 1140 screen = Screen.new(30, 7) 1141 screen:add_extra_attr_ids({ 1142 [100] = { foreground = tonumber('0xe00000'), fg_indexed = true }, 1143 [101] = { foreground = Screen.colors.White, background = Screen.colors.DarkGreen }, 1144 [102] = { 1145 bold = true, 1146 foreground = Screen.colors.White, 1147 background = Screen.colors.DarkGreen, 1148 }, 1149 }) 1150 api.nvim_buf_set_lines(0, 0, -1, true, { '\027[31mTEST\027[0m 0' }) 1151 feed('yy99pG$<C-V>98kg<C-A>') 1152 screen:expect([[ 1153 {18:^[}[31mTEST{18:^[}[0m 0 | 1154 {18:^[}[31mTEST{18:^[}[0m ^1 | 1155 {18:^[}[31mTEST{18:^[}[0m 2 | 1156 {18:^[}[31mTEST{18:^[}[0m 3 | 1157 {18:^[}[31mTEST{18:^[}[0m 4 | 1158 {18:^[}[31mTEST{18:^[}[0m 5 | 1159 99 lines changed | 1160 ]]) 1161 buf = api.nvim_get_current_buf() 1162 win = api.nvim_get_current_win() 1163 command('set winwidth=10 | 10vnew') 1164 end) 1165 1166 local function check_buffer_lines(start, stop) 1167 local lines = api.nvim_buf_get_lines(buf, 0, -1, true) 1168 for i = start, stop do 1169 eq(('TEST %d'):format(i), lines[i - start + 1]) 1170 end 1171 eq('', lines[#lines]) 1172 eq(stop - start + 2, #lines) 1173 end 1174 1175 local function check_common() 1176 feed('<C-W>lG') 1177 screen:expect([[ 1178 │{100:TEST} 96 | 1179 {1:~ }│{100:TEST} 97 | 1180 {1:~ }│{100:TEST} 98 | 1181 {1:~ }│{100:TEST} 99 | 1182 {1:~ }│^ | 1183 {2:[No Name] }{102:[Scratch] [-] }| 1184 99 lines changed | 1185 ]]) 1186 end 1187 1188 it('with nvim_open_term() on buffer with fewer lines than scrollback', function() 1189 exec_lua(function() 1190 vim.api.nvim_open_term(buf, {}) 1191 vim.api.nvim_win_set_cursor(win, { 3, 0 }) 1192 end) 1193 screen:expect([[ 1194 ^ │{100:TEST} 0 | 1195 {1:~ }│{100:TEST} 1 | 1196 {1:~ }│{100:TEST} 2 | 1197 {1:~ }│{100:TEST} 3 | 1198 {1:~ }│{100:TEST} 4 | 1199 {3:[No Name] }{101:[Scratch] [-] }| 1200 99 lines changed | 1201 ]]) 1202 eq({ 3, 0 }, api.nvim_win_get_cursor(win)) 1203 check_buffer_lines(0, 99) 1204 check_common() 1205 end) 1206 1207 it('with nvim_open_term() on buffer with more lines than scrollback', function() 1208 api.nvim_set_option_value('scrollback', 10, { buf = buf }) 1209 exec_lua(function() 1210 vim.api.nvim_open_term(buf, {}) 1211 vim.api.nvim_win_set_cursor(win, { 3, 3 }) 1212 end) 1213 screen:expect([[ 1214 ^ │{100:TEST} 86 | 1215 {1:~ }│{100:TEST} 87 | 1216 {1:~ }│{100:TEST} 88 | 1217 {1:~ }│{100:TEST} 89 | 1218 {1:~ }│{100:TEST} 90 | 1219 {3:[No Name] }{101:[Scratch] [-] }| 1220 99 lines changed | 1221 ]]) 1222 eq({ 1, 0 }, api.nvim_win_get_cursor(win)) 1223 check_buffer_lines(86, 99) 1224 check_common() 1225 end) 1226 1227 describe('when window height', function() 1228 before_each(function() 1229 feed('<C-W>lGV4kdgg') 1230 screen:try_resize(30, 20) 1231 command('botright 9new | wincmd p') 1232 exec_lua(function() 1233 vim.g.chan = vim.api.nvim_open_term(buf, {}) 1234 vim.cmd('$') 1235 end) 1236 screen:expect([[ 1237 │{100:TEST} 88 | 1238 {1:~ }│{100:TEST} 89 | 1239 {1:~ }│{100:TEST} 90 | 1240 {1:~ }│{100:TEST} 91 | 1241 {1:~ }│{100:TEST} 92 | 1242 {1:~ }│{100:TEST} 93 | 1243 {1:~ }│{100:TEST} 94 | 1244 {1:~ }│^ | 1245 {2:[No Name] }{102:[Scratch] [-] }| 1246 | 1247 {1:~ }|*8 1248 {2:[No Name] }| 1249 | 1250 ]]) 1251 check_buffer_lines(0, 94) 1252 end) 1253 1254 local send_cmd = 'call chansend(g:chan, @")' 1255 1256 describe('increases in the same refresh cycle as outputting lines', function() 1257 --- @type string[][] 1258 local perms = t.concat_tables( 1259 t.permutations({ 'resize +2', send_cmd }), 1260 t.permutations({ 'resize +4', 'resize -2', send_cmd }), 1261 t.permutations({ 'resize +6', 'resize -4', send_cmd }) 1262 ) 1263 assert(#perms == 2 + 6 + 6) 1264 local screen_final = [[ 1265 │{100:TEST} 91 | 1266 {1:~ }│{100:TEST} 92 | 1267 {1:~ }│{100:TEST} 93 | 1268 {1:~ }│{100:TEST} 94 | 1269 {1:~ }│{100:TEST} 95 | 1270 {1:~ }│{100:TEST} 96 | 1271 {1:~ }│{100:TEST} 97 | 1272 {1:~ }│{100:TEST} 98 | 1273 {1:~ }│{100:TEST} 99 | 1274 {1:~ }│^ | 1275 {2:[No Name] }{102:[Scratch] [-] }| 1276 | 1277 {1:~ }|*6 1278 {2:[No Name] }| 1279 | 1280 ]] 1281 1282 for i, perm in ipairs(perms) do 1283 it(('permutation %d'):format(i), function() 1284 exec_lua(function() 1285 for _, cmd in ipairs(perm) do 1286 vim.cmd(cmd) 1287 end 1288 end) 1289 screen:expect(screen_final) 1290 check_buffer_lines(0, 99) 1291 end) 1292 end 1293 1294 describe('with full scrollback,', function() 1295 before_each(function() 1296 api.nvim_set_option_value('scrollback', 6, { buf = buf }) 1297 check_buffer_lines(82, 94) 1298 end) 1299 1300 it('output first', function() 1301 command(send_cmd .. ' | resize +2') 1302 screen:expect(screen_final) 1303 check_buffer_lines(87, 99) 1304 end) 1305 1306 it('resize first', function() 1307 command('resize +2 | ' .. send_cmd) 1308 screen:expect(screen_final) 1309 check_buffer_lines(85, 99) 1310 end) 1311 end) 1312 end) 1313 1314 describe('decreases in the same refresh cycle as outputting lines', function() 1315 --- @type string[][] 1316 local perms = t.concat_tables( 1317 t.permutations({ 'resize -2', send_cmd }), 1318 t.permutations({ 'resize -4', 'resize +2', send_cmd }), 1319 t.permutations({ 'resize -6', 'resize +4', send_cmd }) 1320 ) 1321 assert(#perms == 2 + 6 + 6) 1322 local screen_final = [[ 1323 │{100:TEST} 95 | 1324 {1:~ }│{100:TEST} 96 | 1325 {1:~ }│{100:TEST} 97 | 1326 {1:~ }│{100:TEST} 98 | 1327 {1:~ }│{100:TEST} 99 | 1328 {1:~ }│^ | 1329 {2:[No Name] }{102:[Scratch] [-] }| 1330 | 1331 {1:~ }|*10 1332 {2:[No Name] }| 1333 | 1334 ]] 1335 1336 for i, perm in ipairs(perms) do 1337 it(('permutation %d'):format(i), function() 1338 exec_lua(function() 1339 for _, cmd in ipairs(perm) do 1340 vim.cmd(cmd) 1341 end 1342 end) 1343 screen:expect(screen_final) 1344 check_buffer_lines(0, 99) 1345 end) 1346 end 1347 end) 1348 1349 describe("decreases by more than 'scrollback'", function() 1350 before_each(function() 1351 api.nvim_set_option_value('scrollback', 4, { buf = buf }) 1352 check_buffer_lines(84, 94) 1353 end) 1354 1355 local perms = { 1356 { send_cmd, 'resize -6' }, 1357 { 'resize -6', send_cmd }, 1358 { send_cmd, 'resize +6', 'resize -12' }, 1359 { 'resize +6', send_cmd, 'resize -12' }, 1360 { 'resize +6', 'resize -12', send_cmd }, 1361 } 1362 local screen_final = [[ 1363 │{100:TEST} 99 | 1364 {1:~ }│^ | 1365 {2:[No Name] }{102:[Scratch] [-] }| 1366 | 1367 {1:~ }|*14 1368 {2:[No Name] }| 1369 | 1370 ]] 1371 1372 for i, perm in ipairs(perms) do 1373 it(('permutation %d'):format(i), function() 1374 exec_lua(function() 1375 for _, cmd in ipairs(perm) do 1376 vim.cmd(cmd) 1377 end 1378 end) 1379 screen:expect(screen_final) 1380 check_buffer_lines(95, 99) 1381 end) 1382 end 1383 end) 1384 end) 1385 end)