fix(terminal): losing output if BufFile* poll for events (#37580)

Problem:  Terminal loses output if a BufFilePre or BufFilePost autocmd
          polls for events.
Solution: Rename the buffer after allocating the terminal instance. Also
          fix buffer getting wrong name if BufFilePre uses NameBuff.
This commit is contained in:
zeertzjq
2026-01-28 06:21:28 +08:00
committed by GitHub
parent d96a88252e
commit df21ac729c
6 changed files with 141 additions and 55 deletions

View File

@@ -801,10 +801,9 @@ describe(':terminal buffer', function()
check_term_rep_20000('REPFAST')
end)
-- it('does not drop data when autocommands poll for events #37559', function()
it('does not drop data when TermOpen polls for events', function()
-- api.nvim_create_autocmd('BufFilePre', { command = 'sleep 50m', nested = true })
-- api.nvim_create_autocmd('BufFilePost', { command = 'sleep 50m', nested = true })
it('does not drop data when autocommands poll for events #37559', function()
api.nvim_create_autocmd('BufFilePre', { command = 'sleep 50m', nested = true })
api.nvim_create_autocmd('BufFilePost', { command = 'sleep 50m', nested = true })
api.nvim_create_autocmd('TermOpen', { command = 'sleep 50m', nested = true })
-- REP pauses 1 ms every 100 lines, so each autocommand processes some output.
check_term_rep_20000('REP')
@@ -1144,6 +1143,26 @@ describe(':terminal buffer', function()
eq('OTHER_TITLE', api.nvim_buf_get_var(0, 'term_title'))
matches('^E937: ', api.nvim_get_vvar('errmsg'))
end)
it('using NameBuff in BufFilePre does not interfere with buffer rename', function()
local oldbuf = api.nvim_get_current_buf()
n.exec([[
file Xoldfile
new Xotherfile
wincmd w
let g:BufFilePre_bufs = []
let g:BufFilePost_bufs = []
autocmd BufFilePre * call add(g:BufFilePre_bufs, [bufnr(), bufname()])
autocmd BufFilePost * call add(g:BufFilePost_bufs, [bufnr(), bufname()])
autocmd BufFilePre,BufFilePost * call execute('ls')
]])
fn.jobstart({ testprg('shell-test') }, { term = true })
eq({ { oldbuf, 'Xoldfile' } }, api.nvim_get_var('BufFilePre_bufs'))
local buffilepost_bufs = api.nvim_get_var('BufFilePost_bufs')
eq(1, #buffilepost_bufs)
eq(oldbuf, buffilepost_bufs[1][1])
matches('^term://', buffilepost_bufs[1][2])
end)
end)
describe('on_lines does not emit out-of-bounds line indexes when', function()

View File

@@ -138,41 +138,53 @@ it('chansend sends lines to terminal channel in proper order', function()
end
end)
describe('no crash when TermOpen autocommand', function()
local screen
--- @param event string
--- @param extra_tests fun(table, table)?
local function test_autocmd_no_crash(event, extra_tests)
local env = {}
-- Use REPFAST for immediately output after start.
local term_args = { testprg('shell-test'), 'REPFAST', '50', 'TEST' }
before_each(function()
clear()
screen = Screen.new(60, 4)
command([[call setline(1, 'OLDBUF') | enew]])
env.screen = Screen.new(60, 4)
command([[file Xoldbuf | call setline(1, 'OLDBUF') | enew]])
-- Wait before :bwipe to avoid closing PTY master before the child calls setsid(),
-- as that will cause SIGHUP to be also sent to the parent.
-- Use vim.uv.sleep() which blocks the event loop.
n.exec([[
func Wipe()
lua vim.uv.sleep(5)
bwipe!
endfunc
]])
end)
it('processes job exit event when using jobstart(…,{term=true})', function()
command([[autocmd TermOpen * call input('')]])
api.nvim_create_autocmd(event, { command = "call input('')" })
async_meths.nvim_call_function('jobstart', { term_args, { term = true } })
screen:expect([[
env.screen:expect([[
|
{1:~ }|*2
^ |
]])
vim.uv.sleep(20)
feed('<CR>')
screen:expect([[
env.screen:expect([[
^0: TEST |
1: TEST |
2: TEST |
|
]])
feed('i')
screen:expect([[
env.screen:expect([[
49: TEST |
|
[Process exited 0]^ |
{5:-- TERMINAL --} |
]])
feed('<CR>')
screen:expect([[
env.screen:expect([[
^OLDBUF |
{1:~ }|*2
|
@@ -181,50 +193,69 @@ describe('no crash when TermOpen autocommand', function()
end)
it('wipes buffer and processes events when using jobstart(…,{term=true})', function()
command([[autocmd TermOpen * bwipe! | call input('')]])
api.nvim_create_autocmd(event, { command = "call Wipe() | call input('')" })
async_meths.nvim_call_function('jobstart', { term_args, { term = true } })
screen:expect([[
env.screen:expect([[
|
{1:~ }|*2
^ |
]])
vim.uv.sleep(20)
feed('<CR>')
screen:expect([[
env.screen:expect([[
^OLDBUF |
{1:~ }|*2
|
]])
assert_alive()
eq('Xoldbuf', eval('bufname()'))
eq(0, eval([[exists('b:term_title')]]))
end)
it('wipes buffer and processes events when using nvim_open_term()', function()
command([[autocmd TermOpen * bwipe! | call input('')]])
async_meths.nvim_open_term(0, {})
screen:expect([[
|
{1:~ }|*2
^ |
]])
feed('<CR>')
screen:expect([[
^OLDBUF |
{1:~ }|*2
|
]])
assert_alive()
end)
if extra_tests then
extra_tests(env, term_args)
end
end
it('wipes buffer when using jobstart(…,{term=true}) during Nvim exit', function()
n.expect_exit(n.exec_lua, function()
vim.schedule(function()
vim.fn.jobstart(term_args, { term = true })
describe('no crash when TermOpen autocommand', function()
test_autocmd_no_crash('TermOpen', function(env, term_args)
it('wipes buffer and processes events when using nvim_open_term()', function()
api.nvim_create_autocmd('TermOpen', { command = "call Wipe() | call input('')" })
async_meths.nvim_open_term(0, {})
env.screen:expect([[
|
{1:~ }|*2
^ |
]])
feed('<CR>')
env.screen:expect([[
^OLDBUF |
{1:~ }|*2
|
]])
assert_alive()
end)
it('wipes buffer when using jobstart(…,{term=true}) during Nvim exit', function()
n.expect_exit(n.exec_lua, function()
vim.schedule(function()
vim.fn.jobstart(term_args, { term = true })
end)
vim.api.nvim_create_autocmd('TermOpen', { command = 'call Wipe()' })
vim.cmd('qall!')
end)
vim.cmd('autocmd TermOpen * bwipe!')
vim.cmd('qall!')
end)
end)
end)
describe('no crash when BufFilePre autocommand', function()
test_autocmd_no_crash('BufFilePre')
end)
describe('no crash when BufFilePost autocommand', function()
test_autocmd_no_crash('BufFilePost')
end)
describe('nvim_open_term', function()
local screen