local t = require('test.testutil') local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') local tt = require('test.functional.testterm') local clear, eq, neq = n.clear, t.eq, t.neq local feed, testprg = n.feed, n.testprg local fn = n.fn local eval = n.eval local command = n.command local poke_eventloop = n.poke_eventloop local retry = t.retry local api = n.api local feed_data = tt.feed_data local pcall_err = t.pcall_err local exec_lua = n.exec_lua local assert_alive = n.assert_alive local skip = t.skip local is_os = t.is_os local function test_terminal_scrollback(hide_curbuf) local screen --- @type test.functional.ui.screen local buf --- @type integer local chan --- @type integer local otherbuf --- @type integer local restore_terminal_mode --- @type boolean? local save_feed_data = feed_data local function may_hide_curbuf() if hide_curbuf then eq(nil, restore_terminal_mode) restore_terminal_mode = vim.startswith(api.nvim_get_mode().mode, 't') api.nvim_set_current_buf(otherbuf) end end local function may_restore_curbuf() if hide_curbuf then neq(nil, restore_terminal_mode) eq(buf, fn.bufnr('#')) feed('') -- "view" in 'jumpoptions' applies to this if restore_terminal_mode then feed('i') else -- Cursor position was restored from wi_mark, not b_last_cursor. -- Check that b_last_cursor and wi_mark are the same. local last_cursor = fn.getpos([['"]]) local restored_cursor = fn.getpos('.') if last_cursor[2] > 0 then eq(restored_cursor, last_cursor) else eq({ 0, 0, 0, 0 }, last_cursor) eq({ 0, 1, 1, 0 }, restored_cursor) end end restore_terminal_mode = nil end end setup(function() feed_data = function(data) may_hide_curbuf() api.nvim_chan_send(chan, data) may_restore_curbuf() end end) teardown(function() feed_data = save_feed_data end) --- @param prefix string --- @param start integer --- @param stop integer local function feed_lines(prefix, start, stop) may_hide_curbuf() local data = '' for i = start, stop do data = data .. prefix .. tostring(i) .. '\n' end api.nvim_chan_send(chan, data) retry(nil, 1000, function() eq({ prefix .. tostring(stop), '' }, api.nvim_buf_get_lines(buf, -3, -1, true)) end) may_restore_curbuf() end local function try_resize(width, height) may_hide_curbuf() screen:try_resize(width, height) may_restore_curbuf() end before_each(function() clear() command('set nostartofline jumpoptions+=view') screen = tt.setup_screen(nil, nil, 30) buf = api.nvim_get_current_buf() chan = api.nvim_get_option_value('channel', { buf = buf }) if hide_curbuf then otherbuf = api.nvim_create_buf(true, false) end end) describe('when the limit is exceeded', function() before_each(function() feed_lines('line', 1, 30) screen:expect([[ line26 | line27 | line28 | line29 | line30 | ^ | {5:-- TERMINAL --} | ]]) eq(16, api.nvim_buf_line_count(0)) end) it('will delete extra lines at the top', function() feed('gg') screen:expect([[ ^line16 | line17 | line18 | line19 | line20 | line21 | | ]]) end) describe('and cursor on non-last row in screen', function() before_each(function() feed([[M$]]) fn.setpos("'m", { 0, 13, 4, 0 }) local ns = api.nvim_create_namespace('test') api.nvim_buf_set_extmark(0, ns, 12, 0, { end_col = 6, hl_group = 'ErrorMsg' }) screen:expect([[ line26 | line27 | {101:line2^8} | line29 | line30 | |*2 ]]) end) it("outputting fewer than 'scrollback' lines", function() feed_lines('new_line', 1, 6) screen:expect([[ line26 | line27 | {101:line2^8} | line29 | line30 | new_line1 | | ]]) eq({ 0, 7, 4, 0 }, fn.getpos("'m")) eq({ 0, 7, 6, 0 }, fn.getpos('.')) end) it("outputting more than 'scrollback' lines", function() feed_lines('new_line', 1, 11) screen:expect([[ line27 | {101:line2^8} | line29 | line30 | new_line1 | new_line2 | | ]]) eq({ 0, 2, 4, 0 }, fn.getpos("'m")) eq({ 0, 2, 6, 0 }, fn.getpos('.')) end) it('outputting more lines than whole buffer', function() feed_lines('new_line', 1, 20) screen:expect([[ ^new_line6 | new_line7 | new_line8 | new_line9 | new_line10 | new_line11 | | ]]) eq({ 0, 0, 0, 0 }, fn.getpos("'m")) eq({ 0, 1, 1, 0 }, fn.getpos('.')) end) it('clearing scrollback with ED 3', function() feed_data('\027[3J') screen:expect_unchanged(hide_curbuf) eq({ 0, 3, 4, 0 }, fn.getpos("'m")) eq({ 0, 3, 6, 0 }, fn.getpos('.')) feed('gg') screen:expect([[ line2^6 | line27 | {101:line28} | line29 | line30 | |*2 ]]) end) it('clearing scrollback with ED 3 and outputting lines', function() feed_data('\027[3J' .. 'new_line1\nnew_line2\nnew_line3') screen:expect([[ line26 | line27 | {101:line2^8} | line29 | line30 | new_line1 | | ]]) eq({ 0, 3, 4, 0 }, fn.getpos("'m")) eq({ 0, 3, 6, 0 }, fn.getpos('.')) end) it('clearing scrollback with ED 3 between outputting lines', function() skip(is_os('win'), 'FIXME: wrong behavior on Windows, ConPTY bug?') feed_data('line31\nline32\n' .. '\027[3J' .. 'new_line1\nnew_line2') screen:expect([[ {101:line2^8} | line29 | line30 | line31 | line32 | new_line1 | | ]]) eq({ 0, 1, 4, 0 }, fn.getpos("'m")) eq({ 0, 1, 6, 0 }, fn.getpos('.')) end) end) describe('and cursor on scrollback row #12651', function() before_each(function() feed([[Hk$]]) fn.setpos("'m", { 0, 10, 4, 0 }) local ns = api.nvim_create_namespace('test') api.nvim_buf_set_extmark(0, ns, 9, 0, { end_col = 6, hl_group = 'ErrorMsg' }) screen:expect([[ {101:line2^5} | line26 | line27 | line28 | line29 | line30 | | ]]) end) it("outputting fewer than 'scrollback' lines", function() feed_lines('new_line', 1, 6) screen:expect_unchanged(hide_curbuf) eq({ 0, 4, 4, 0 }, fn.getpos("'m")) eq({ 0, 4, 6, 0 }, fn.getpos('.')) end) it("outputting more than 'scrollback' lines", function() feed_lines('new_line', 1, 11) screen:expect([[ ^line27 | line28 | line29 | line30 | new_line1 | new_line2 | | ]]) eq({ 0, 0, 0, 0 }, fn.getpos("'m")) eq({ 0, 1, 1, 0 }, fn.getpos('.')) end) end) it('changing window height does not duplicate lines', function() -- XXX: Can't test this reliably on Windows unless the cursor is _moved_ -- by the resize. http://docs.libuv.org/en/v1.x/signal.html -- See also: https://github.com/rprichard/winpty/issues/110 skip(is_os('win')) try_resize(screen._width, screen._height + 4) screen:expect([[ line23 | line24 | line25 | line26 | line27 | line28 | line29 | line30 | rows: 10, cols: 30 | ^ | {5:-- TERMINAL --} | ]]) eq(17, api.nvim_buf_line_count(0)) try_resize(screen._width, screen._height - 2) screen:expect([[ line26 | line27 | line28 | line29 | line30 | rows: 10, cols: 30 | rows: 8, cols: 30 | ^ | {5:-- TERMINAL --} | ]]) eq(18, api.nvim_buf_line_count(0)) try_resize(screen._width, screen._height - 3) screen:expect([[ line30 | rows: 10, cols: 30 | rows: 8, cols: 30 | rows: 5, cols: 30 | ^ | {5:-- TERMINAL --} | ]]) eq(15, api.nvim_buf_line_count(0)) try_resize(screen._width, screen._height + 3) screen:expect([[ line28 | line29 | line30 | rows: 10, cols: 30 | rows: 8, cols: 30 | rows: 5, cols: 30 | rows: 8, cols: 30 | ^ | {5:-- TERMINAL --} | ]]) eq(16, api.nvim_buf_line_count(0)) feed([[8]]) screen:expect([[ line20 | line21 | line22 | line23 | line24 | line25 | line26 | ^line27 | | ]]) end) end) describe('with cursor at last row', function() before_each(function() feed_lines('line', 1, 4) screen:expect([[ tty ready | line1 | line2 | line3 | line4 | ^ | {5:-- TERMINAL --} | ]]) fn.setpos("'m", { 0, 3, 4, 0 }) local ns = api.nvim_create_namespace('test') api.nvim_buf_set_extmark(0, ns, 2, 0, { end_col = 5, hl_group = 'ErrorMsg' }) screen:expect([[ tty ready | line1 | {101:line2} | line3 | line4 | ^ | {5:-- TERMINAL --} | ]]) end) it("outputting more than 'scrollback' lines in Normal mode", function() feed([[]]) feed_lines('new_line', 1, 11) screen:expect([[ new_line7 | new_line8 | new_line9 | new_line10 | new_line11 | ^ | | ]]) feed('gg') screen:expect([[ ^line1 | {101:line2} | line3 | line4 | new_line1 | new_line2 | | ]]) eq({ 0, 2, 4, 0 }, fn.getpos("'m")) feed('G') feed_lines('new_line', 12, 31) screen:expect([[ new_line27 | new_line28 | new_line29 | new_line30 | new_line31 | ^ | | ]]) feed('gg') screen:expect([[ ^new_line17 | new_line18 | new_line19 | new_line20 | new_line21 | new_line22 | | ]]) eq({ 0, 0, 0, 0 }, fn.getpos("'m")) end) it('clearing scrollback with ED 3', function() -- Clearing empty scrollback and then outputting a line feed_data('\027[3J' .. 'line5\n') screen:expect([[ line1 | {101:line2} | line3 | line4 | line5 | ^ | {5:-- TERMINAL --} | ]]) eq(7, api.nvim_buf_line_count(0)) eq({ 0, 3, 4, 0 }, fn.getpos("'m")) -- Clearing 1 line of scrollback feed_data('\027[3J') screen:expect_unchanged(hide_curbuf) eq(6, api.nvim_buf_line_count(0)) eq({ 0, 2, 4, 0 }, fn.getpos("'m")) -- Outputting a line feed_data('line6\n') screen:expect([[ {101:line2} | line3 | line4 | line5 | line6 | ^ | {5:-- TERMINAL --} | ]]) eq(7, api.nvim_buf_line_count(0)) eq({ 0, 2, 4, 0 }, fn.getpos("'m")) -- Clearing 1 line of scrollback and then outputting a line feed_data('\027[3J' .. 'line7\n') screen:expect([[ line3 | line4 | line5 | line6 | line7 | ^ | {5:-- TERMINAL --} | ]]) eq(7, api.nvim_buf_line_count(0)) eq({ 0, 1, 4, 0 }, fn.getpos("'m")) -- Check first line of buffer in Normal mode feed([[gg]]) screen:expect([[ {101:^line2} | line3 | line4 | line5 | line6 | line7 | | ]]) feed('G') -- Outputting lines and then clearing scrollback skip(is_os('win'), 'FIXME: wrong behavior on Windows, ConPTY bug?') feed_data('line8\nline9\n' .. '\027[3J') screen:expect([[ line5 | line6 | line7 | line8 | line9 | ^ | | ]]) eq(6, api.nvim_buf_line_count(0)) eq({ 0, 0, 0, 0 }, fn.getpos("'m")) end) describe('and 1 line is printed', function() before_each(function() feed_lines('line', 5, 5) end) it('will hide the top line', function() screen:expect([[ line1 | {101:line2} | line3 | line4 | line5 | ^ | {5:-- TERMINAL --} | ]]) eq(7, api.nvim_buf_line_count(0)) eq({ 0, 3, 4, 0 }, fn.getpos("'m")) end) describe('and then 3 more lines are printed', function() before_each(function() feed_lines('line', 6, 8) end) it('will hide the top 4 lines', function() screen:expect([[ line4 | line5 | line6 | line7 | line8 | ^ | {5:-- TERMINAL --} | ]]) eq({ 0, 3, 4, 0 }, fn.getpos("'m")) feed('6k') screen:expect([[ ^line3 | line4 | line5 | line6 | line7 | line8 | | ]]) feed('gg') screen:expect([[ ^tty ready | line1 | {101:line2} | line3 | line4 | line5 | | ]]) feed('G') screen:expect([[ line4 | line5 | line6 | line7 | line8 | ^ | | ]]) end) end) end) describe('and height decreased by 1', function() local function will_hide_top_line() feed([[]]) try_resize(screen._width - 2, screen._height - 1) screen:expect([[ {101:line2} | line3 | line4 | rows: 5, cols: 28 | ^ | | ]]) eq({ 0, 3, 4, 0 }, fn.getpos("'m")) end it('will hide top line', will_hide_top_line) describe('and then decreased by 2', function() before_each(function() will_hide_top_line() try_resize(screen._width - 2, screen._height - 2) end) it('will hide the top 3 lines', function() screen:expect([[ rows: 5, cols: 28 | rows: 3, cols: 26 | ^ | | ]]) eq(8, api.nvim_buf_line_count(0)) eq({ 0, 3, 4, 0 }, fn.getpos("'m")) feed('3k') screen:expect([[ ^line4 | rows: 5, cols: 28 | rows: 3, cols: 26 | | ]]) feed('gg') screen:expect([[ ^tty ready | line1 | {101:line2} | | ]]) end) end) end) end) describe('with empty lines after the cursor', function() -- XXX: Can't test this reliably on Windows unless the cursor is _moved_ -- by the resize. http://docs.libuv.org/en/v1.x/signal.html -- See also: https://github.com/rprichard/winpty/issues/110 if skip(is_os('win')) then return end describe('and the height is decreased by 2', function() before_each(function() try_resize(screen._width, screen._height - 2) end) local function will_delete_last_two_lines() screen:expect([[ tty ready | rows: 4, cols: 30 | ^ | | {5:-- TERMINAL --} | ]]) eq(4, api.nvim_buf_line_count(0)) end it('will delete the last two empty lines', will_delete_last_two_lines) describe('and then decreased by 1', function() before_each(function() will_delete_last_two_lines() try_resize(screen._width, screen._height - 1) end) it('will delete the last line and hide the first', function() screen:expect([[ rows: 4, cols: 30 | rows: 3, cols: 30 | ^ | {5:-- TERMINAL --} | ]]) eq(4, api.nvim_buf_line_count(0)) feed('gg') screen:expect([[ ^tty ready | rows: 4, cols: 30 | rows: 3, cols: 30 | | ]]) feed('a') screen:expect([[ rows: 4, cols: 30 | rows: 3, cols: 30 | ^ | {5:-- TERMINAL --} | ]]) end) end) end) end) describe('with 4 lines hidden in the scrollback', function() before_each(function() feed_lines('line', 1, 4) screen:expect([[ tty ready | line1 | line2 | line3 | line4 | ^ | {5:-- TERMINAL --} | ]]) fn.setpos("'m", { 0, 3, 4, 0 }) local ns = api.nvim_create_namespace('test') api.nvim_buf_set_extmark(0, ns, 2, 0, { end_col = 5, hl_group = 'ErrorMsg' }) screen:expect([[ tty ready | line1 | {101:line2} | line3 | line4 | ^ | {5:-- TERMINAL --} | ]]) try_resize(screen._width, screen._height - 3) screen:expect([[ line4 | rows: 3, cols: 30 | ^ | {5:-- TERMINAL --} | ]]) eq(7, api.nvim_buf_line_count(0)) end) describe('and the height is increased by 1', function() -- XXX: Can't test this reliably on Windows unless the cursor is _moved_ -- by the resize. http://docs.libuv.org/en/v1.x/signal.html -- See also: https://github.com/rprichard/winpty/issues/110 if skip(is_os('win')) then return end local function pop_then_push() try_resize(screen._width, screen._height + 1) screen:expect([[ line4 | rows: 3, cols: 30 | rows: 4, cols: 30 | ^ | {5:-- TERMINAL --} | ]]) eq({ 0, 3, 4, 0 }, fn.getpos("'m")) end it('will pop 1 line and then push it back', pop_then_push) describe('and then by 3', function() before_each(function() pop_then_push() eq(8, api.nvim_buf_line_count(0)) try_resize(screen._width, screen._height + 3) end) local function pop3_then_push1() screen:expect([[ {101:line2} | line3 | line4 | rows: 3, cols: 30 | rows: 4, cols: 30 | rows: 7, cols: 30 | ^ | {5:-- TERMINAL --} | ]]) eq(9, api.nvim_buf_line_count(0)) eq({ 0, 3, 4, 0 }, fn.getpos("'m")) feed('gg') screen:expect([[ ^tty ready | line1 | {101:line2} | line3 | line4 | rows: 3, cols: 30 | rows: 4, cols: 30 | | ]]) end it('will pop 3 lines and then push one back', pop3_then_push1) describe('and then by 4', function() before_each(function() pop3_then_push1() feed('Gi') try_resize(screen._width, screen._height + 4) end) it('will show all lines and leave a blank one at the end', function() screen:expect([[ tty ready | line1 | {101:line2} | line3 | line4 | rows: 3, cols: 30 | rows: 4, cols: 30 | rows: 7, cols: 30 | rows: 11, cols: 30 | ^ | | {5:-- TERMINAL --} | ]]) -- since there's an empty line after the cursor, the buffer line -- count equals the terminal screen height eq(11, api.nvim_buf_line_count(0)) eq({ 0, 3, 4, 0 }, fn.getpos("'m")) end) end) end) end) end) it('reducing &scrollback deletes extra lines immediately', function() feed_lines('line', 1, 30) screen:expect([[ line26 | line27 | line28 | line29 | line30 | ^ | {5:-- TERMINAL --} | ]]) local term_height = 6 -- Actual terminal screen height, not the scrollback -- Initial local scrollback = api.nvim_get_option_value('scrollback', { buf = buf }) eq(scrollback + term_height, fn.line('$')) eq(scrollback + term_height, fn.line('.')) n.fn.setpos("'m", { 0, scrollback + 1, 4, 0 }) local ns = api.nvim_create_namespace('test') api.nvim_buf_set_extmark(0, ns, scrollback, 0, { end_col = 6, hl_group = 'ErrorMsg' }) screen:expect([[ {101:line26} | line27 | line28 | line29 | line30 | ^ | {5:-- TERMINAL --} | ]]) -- Reduction scrollback = scrollback - 2 may_hide_curbuf() api.nvim_set_option_value('scrollback', scrollback, { buf = buf }) may_restore_curbuf() eq(scrollback + term_height, fn.line('$')) eq(scrollback + term_height, fn.line('.')) screen:expect_unchanged(hide_curbuf) eq({ 0, scrollback + 1, 4, 0 }, n.fn.getpos("'m")) end) end describe(':terminal scrollback', function() describe('in current buffer', function() test_terminal_scrollback(false) end) describe('in hidden buffer', function() test_terminal_scrollback(true) end) end) describe(':terminal prints more lines than the screen height and exits', function() it('will push extra lines to scrollback', function() clear() local screen = Screen.new(30, 7, { rgb = false }) screen:add_extra_attr_ids({ [100] = { foreground = 12 } }) command( ("call jobstart(['%s', '10'], {'term':v:true}) | startinsert"):format(testprg('tty-test')) ) screen:expect([[ line6 | line7 | line8 | line9 | | [Process exited 0]^ | {5:-- TERMINAL --} | ]]) feed('') -- closes the buffer correctly after pressing a key screen:expect([[ ^ | {100:~ }|*5 | ]]) end) end) describe("'scrollback' option", function() before_each(function() clear() end) local function set_fake_shell() api.nvim_set_option_value('shell', string.format('"%s" INTERACT', testprg('shell-test')), {}) end local function expect_lines(expected, epsilon) local ep = epsilon and epsilon or 0 local actual = eval("line('$')") if expected > actual + ep and expected < actual - ep then error('expected (+/- ' .. ep .. '): ' .. expected .. ', actual: ' .. tostring(actual)) end end it('set to 0 behaves as 1', function() local screen if is_os('win') then screen = tt.setup_screen(nil, { 'cmd.exe' }, 30) else screen = tt.setup_screen(nil, { 'sh' }, 30) end api.nvim_set_option_value('scrollback', 0, {}) feed_data(('%s REP 31 line%s'):format(testprg('shell-test'), is_os('win') and '\r' or '\n')) screen:expect { any = '30: line ' } retry(nil, nil, function() expect_lines(7) end) end) it('deletes lines (only) if necessary', function() local screen if is_os('win') then command([[let $PROMPT='$$']]) screen = tt.setup_screen(nil, { 'cmd.exe' }, 30) else command('let $PS1 = "$"') screen = tt.setup_screen(nil, { 'sh' }, 30) end api.nvim_set_option_value('scrollback', 200, {}) -- Wait for prompt. screen:expect { any = '%$' } feed_data(('%s REP 31 line%s'):format(testprg('shell-test'), is_os('win') and '\r' or '\n')) screen:expect { any = '30: line ' } retry(nil, nil, function() expect_lines(33, 2) end) api.nvim_set_option_value('scrollback', 10, {}) poke_eventloop() retry(nil, nil, function() expect_lines(16) end) api.nvim_set_option_value('scrollback', 10000, {}) retry(nil, nil, function() expect_lines(16) end) -- Terminal job data is received asynchronously, may happen before the -- 'scrollback' option is synchronized with the internal sb_buffer. command('sleep 100m') feed_data(('%s REP 41 line%s'):format(testprg('shell-test'), is_os('win') and '\r' or '\n')) if is_os('win') then screen:expect([[ 37: line | 38: line | 39: line | 40: line | | $^ | {5:-- TERMINAL --} | ]]) else screen:expect([[ 36: line | 37: line | 38: line | 39: line | 40: line | {MATCH:.*}| {5:-- TERMINAL --} | ]]) end expect_lines(58) -- Verify off-screen state eq((is_os('win') and '36: line' or '35: line'), eval("getline(line('w0') - 1)->trim(' ', 2)")) eq((is_os('win') and '27: line' or '26: line'), eval("getline(line('w0') - 10)->trim(' ', 2)")) end) it('defaults to 10000 in :terminal buffers', function() set_fake_shell() command('terminal') eq(10000, api.nvim_get_option_value('scrollback', {})) end) it('error if set to invalid value', function() eq('Vim(set):E474: Invalid argument: scrollback=-2', pcall_err(command, 'set scrollback=-2')) eq( 'Vim(set):E474: Invalid argument: scrollback=1000001', pcall_err(command, 'set scrollback=1000001') ) end) it('defaults to -1 on normal buffers', function() command('new') eq(-1, api.nvim_get_option_value('scrollback', {})) end) it(':setlocal in a :terminal buffer', function() set_fake_shell() -- _Global_ scrollback=-1 defaults :terminal to 10_000. command('setglobal scrollback=-1') command('terminal') eq(10000, api.nvim_get_option_value('scrollback', {})) -- _Local_ scrollback=-1 in :terminal forces the _maximum_. command('setlocal scrollback=-1') retry(nil, nil, function() -- Fixup happens on refresh, not immediately. eq(1000000, api.nvim_get_option_value('scrollback', {})) end) -- _Local_ scrollback=-1 during TermOpen forces the maximum. #9605 command('setglobal scrollback=-1') command('autocmd TermOpen * setlocal scrollback=-1') command('terminal') eq(1000000, api.nvim_get_option_value('scrollback', {})) end) it(':setlocal in a normal buffer', function() command('new') -- :setlocal to -1. command('setlocal scrollback=-1') eq(-1, api.nvim_get_option_value('scrollback', {})) -- :setlocal to anything except -1. Currently, this just has no effect. command('setlocal scrollback=42') eq(42, api.nvim_get_option_value('scrollback', {})) end) it(':set updates local value and global default', function() set_fake_shell() command('set scrollback=42') -- set global value eq(42, api.nvim_get_option_value('scrollback', {})) command('terminal') eq(42, api.nvim_get_option_value('scrollback', {})) -- inherits global default command('setlocal scrollback=99') eq(99, api.nvim_get_option_value('scrollback', {})) command('set scrollback<') -- reset to global default eq(42, api.nvim_get_option_value('scrollback', {})) command('setglobal scrollback=734') -- new global default eq(42, api.nvim_get_option_value('scrollback', {})) -- local value did not change command('terminal') eq(734, api.nvim_get_option_value('scrollback', {})) end) end) describe('pending scrollback line handling', function() local screen before_each(function() clear() screen = Screen.new(30, 7) end) it("does not crash after setting 'number' #14891", function() exec_lua [[ local api = vim.api local buf = api.nvim_create_buf(true, true) local chan = api.nvim_open_term(buf, {}) vim.wo.number = true api.nvim_chan_send(chan, ("a\n"):rep(11) .. "a") api.nvim_win_set_buf(0, buf) ]] screen:expect [[ {8: 1 }^a | {8: 2 }a | {8: 3 }a | {8: 4 }a | {8: 5 }a | {8: 6 }a | | ]] feed('G') screen:expect [[ {8: 7 }a | {8: 8 }a | {8: 9 }a | {8: 10 }a | {8: 11 }a | {8: 12 }^a | | ]] assert_alive() end) it('does not crash after nvim_buf_call #14891', function() exec_lua( [[ local bufnr = vim.api.nvim_create_buf(false, true) local args = ... vim.api.nvim_buf_call(bufnr, function() vim.fn.jobstart(args, { term = true }) end) vim.api.nvim_win_set_buf(0, bufnr) vim.cmd('startinsert') ]], is_os('win') and { 'cmd.exe', '/c', 'for /L %I in (1,1,12) do @echo hi' } or { 'printf', ('hi\n'):rep(12) } ) screen:expect [[ hi |*4 | [Process exited 0]^ | {5:-- TERMINAL --} | ]] assert_alive() end) end) describe('nvim_open_term()', function() local screen --- @type test.functional.ui.screen local buf --- @type integer local win --- @type integer before_each(function() clear() screen = Screen.new(30, 7) screen:add_extra_attr_ids({ [100] = { foreground = tonumber('0xe00000'), fg_indexed = true }, [101] = { foreground = Screen.colors.White, background = Screen.colors.DarkGreen }, [102] = { bold = true, foreground = Screen.colors.White, background = Screen.colors.DarkGreen, }, }) api.nvim_buf_set_lines(0, 0, -1, true, { '\027[31mTEST\027[0m 0' }) feed('yy99pG$98kg') screen:expect([[ {18:^[}[31mTEST{18:^[}[0m 0 | {18:^[}[31mTEST{18:^[}[0m ^1 | {18:^[}[31mTEST{18:^[}[0m 2 | {18:^[}[31mTEST{18:^[}[0m 3 | {18:^[}[31mTEST{18:^[}[0m 4 | {18:^[}[31mTEST{18:^[}[0m 5 | 99 lines changed | ]]) buf = api.nvim_get_current_buf() win = api.nvim_get_current_win() command('set winwidth=10 | 10vnew') end) local function check_buffer_lines(start, stop) local lines = api.nvim_buf_get_lines(buf, 0, -1, true) for i = start, stop do eq(('TEST %d'):format(i), lines[i - start + 1]) end end local function check_common() feed('lG') screen:expect([[ │{100:TEST} 96 | {1:~ }│{100:TEST} 97 | {1:~ }│{100:TEST} 98 | {1:~ }│{100:TEST} 99 | {1:~ }│^ | {2:[No Name] }{102:[Scratch] [-] }| 99 lines changed | ]]) end it('on buffer with fewer lines than scrollback', function() exec_lua(function() vim.api.nvim_open_term(buf, {}) vim.api.nvim_win_set_cursor(win, { 3, 0 }) end) screen:expect([[ ^ │{100:TEST} 0 | {1:~ }│{100:TEST} 1 | {1:~ }│{100:TEST} 2 | {1:~ }│{100:TEST} 3 | {1:~ }│{100:TEST} 4 | {3:[No Name] }{101:[Scratch] [-] }| 99 lines changed | ]]) eq({ 3, 0 }, api.nvim_win_get_cursor(win)) check_buffer_lines(0, 99) check_common() end) it('on buffer with more lines than scrollback', function() api.nvim_set_option_value('scrollback', 10, { buf = buf }) exec_lua(function() vim.api.nvim_open_term(buf, {}) vim.api.nvim_win_set_cursor(win, { 3, 3 }) end) screen:expect([[ ^ │{100:TEST} 86 | {1:~ }│{100:TEST} 87 | {1:~ }│{100:TEST} 88 | {1:~ }│{100:TEST} 89 | {1:~ }│{100:TEST} 90 | {3:[No Name] }{101:[Scratch] [-] }| 99 lines changed | ]]) eq({ 1, 0 }, api.nvim_win_get_cursor(win)) check_buffer_lines(86, 99) check_common() end) end)