commit 295fb3fdb2a359febc2e75e4eb0f9f438775ab05
parent d1f7672bc9c39bad8000bcf2e2ec6feff8787986
Author: zeertzjq <zeertzjq@outlook.com>
Date: Sat, 10 Jan 2026 08:25:49 +0800
fix(terminal): :edit should respect 'bufhidden' with exited job (#37301)
Diffstat:
2 files changed, 92 insertions(+), 55 deletions(-)
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
@@ -2601,7 +2601,10 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
// oldwin->w_buffer to NULL.
u_sync(false);
const bool did_decrement
- = close_buffer(oldwin, curbuf, (flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD,
+ = close_buffer(oldwin, curbuf,
+ (flags & ECMD_HIDE)
+ || (curbuf->terminal && terminal_running(curbuf->terminal))
+ ? 0 : DOBUF_UNLOAD,
false, false);
// Autocommands may have closed the window.
diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua
@@ -64,26 +64,6 @@ describe(':terminal buffer', function()
eq({ 0, 'line' }, eval('[&l:cursorline, &l:cursorlineopt]'))
end)
- describe('when a new file is edited', function()
- before_each(function()
- feed('<c-\\><c-n>:set bufhidden=wipe<cr>:enew<cr>')
- screen:expect([[
- ^ |
- {100:~ }|*5
- :enew |
- ]])
- end)
-
- it('will hide the buffer, ignoring the bufhidden option', function()
- feed(':bnext:l<esc>')
- screen:expect([[
- ^ |
- {100:~ }|*5
- |
- ]])
- end)
- end)
-
describe('swap and undo', function()
before_each(function()
feed('<c-\\><c-n>')
@@ -880,9 +860,30 @@ describe(':terminal buffer', function()
eq(false, api.nvim_buf_is_valid(term_buf))
end)
+ local enew_screen = [[
+ ^ |
+ {1:~ }|*5
+ |
+ ]]
+
+ local function test_enew_in_buf_with_running_term(env)
+ describe('editing a new file', function()
+ it('hides terminal buffer ignoring bufhidden=wipe', function()
+ local old_snapshot = env.screen:get_snapshot()
+ command('setlocal bufhidden=wipe')
+ command('enew')
+ neq(env.buf, api.nvim_get_current_buf())
+ env.screen:expect(enew_screen)
+ feed('<C-^>')
+ eq(env.buf, api.nvim_get_current_buf())
+ env.screen:expect(old_snapshot)
+ end)
+ end)
+ end
+
local function test_open_term_in_buf_with_running_term(env)
- describe('does not allow', function()
- it('opening another terminal with jobstart() in same buffer', function()
+ describe('does not allow opening another terminal', function()
+ it('with jobstart() in same buffer', function()
eq(
('Vim:Terminal already connected to buffer %d'):format(env.buf),
pcall_err(fn.jobstart, { testprg('tty-test') }, { term = true })
@@ -890,7 +891,7 @@ describe(':terminal buffer', function()
env.screen:expect_unchanged()
end)
- it('opening another terminal with nvim_open_term() in same buffer', function()
+ it('with nvim_open_term() in same buffer', function()
eq(
('Terminal already connected to buffer %d'):format(env.buf),
pcall_err(api.nvim_open_term, env.buf, {})
@@ -904,16 +905,17 @@ describe(':terminal buffer', function()
local env = {}
before_each(function()
- env.screen = Screen.new(60, 6)
+ env.screen = Screen.new(50, 7)
fn.jobstart({ testprg('tty-test') }, { term = true })
env.screen:expect([[
- ^tty ready |
- |*5
+ ^tty ready |
+ |*6
]])
env.buf = api.nvim_get_current_buf()
api.nvim_set_option_value('modified', false, { buf = env.buf })
end)
+ test_enew_in_buf_with_running_term(env)
test_open_term_in_buf_with_running_term(env)
end)
@@ -921,28 +923,58 @@ describe(':terminal buffer', function()
local env = {}
before_each(function()
- env.screen = Screen.new(60, 6)
+ env.screen = Screen.new(50, 7)
local chan = api.nvim_open_term(0, {})
api.nvim_chan_send(chan, 'TEST')
env.screen:expect([[
- ^TEST |
- |*5
+ ^TEST |
+ |*6
]])
env.buf = api.nvim_get_current_buf()
api.nvim_set_option_value('modified', false, { buf = env.buf })
end)
+ test_enew_in_buf_with_running_term(env)
test_open_term_in_buf_with_running_term(env)
end)
- local function test_open_term_in_buf_with_closed_term(env)
- describe('does not leak memory when', function()
- describe('opening another terminal with jobstart() in same buffer', function()
+ local function test_enew_in_buf_with_finished_term(env)
+ describe('editing a new file', function()
+ it('hides terminal buffer with bufhidden=hide', function()
+ local old_snapshot = env.screen:get_snapshot()
+ command('setlocal bufhidden=hide')
+ command('enew')
+ neq(env.buf, api.nvim_get_current_buf())
+ env.screen:expect(enew_screen)
+ feed('<C-^>')
+ eq(env.buf, api.nvim_get_current_buf())
+ env.screen:expect(old_snapshot)
+ end)
+
+ it('wipes terminal buffer with bufhidden=wipe', function()
+ command('setlocal bufhidden=wipe')
+ command('enew')
+ neq(env.buf, api.nvim_get_current_buf())
+ eq(false, api.nvim_buf_is_valid(env.buf))
+ env.screen:expect(enew_screen)
+ feed('<C-^>')
+ env.screen:expect([[
+ ^ |
+ {1:~ }|*5
+ {9:E23: No alternate file} |
+ ]])
+ end)
+ end)
+ end
+
+ local function test_open_term_in_buf_with_finished_term(env)
+ describe('does not leak memory when opening another terminal', function()
+ describe('with jobstart() in same buffer', function()
it('in Normal mode', function()
fn.jobstart({ testprg('tty-test') }, { term = true })
env.screen:expect([[
- ^tty ready |
- |*5
+ ^tty ready |
+ |*6
]])
end)
@@ -951,21 +983,21 @@ describe(':terminal buffer', function()
eq({ blocking = false, mode = 't' }, api.nvim_get_mode())
fn.jobstart({ testprg('tty-test') }, { term = true })
env.screen:expect([[
- tty ready |
- ^ |
- |*3
- {5:-- TERMINAL --} |
+ tty ready |
+ ^ |
+ |*4
+ {5:-- TERMINAL --} |
]])
end)
end)
- describe('opening another terminal with nvim_open_term() in same buffer', function()
+ describe('with nvim_open_term() in same buffer', function()
it('in Normal mode', function()
local chan = api.nvim_open_term(env.buf, {})
api.nvim_chan_send(chan, 'OTHER')
env.screen:expect([[
- ^OTHER |
- |*5
+ ^OTHER |
+ |*6
]])
end)
@@ -975,9 +1007,9 @@ describe(':terminal buffer', function()
local chan = api.nvim_open_term(env.buf, {})
api.nvim_chan_send(chan, 'OTHER')
env.screen:expect([[
- OTHER^ |
- |*4
- {5:-- TERMINAL --} |
+ OTHER^ |
+ |*5
+ {5:-- TERMINAL --} |
]])
end)
end)
@@ -988,38 +1020,40 @@ describe(':terminal buffer', function()
local env = {}
before_each(function()
- env.screen = Screen.new(60, 6)
+ env.screen = Screen.new(50, 7)
fn.jobstart({ testprg('shell-test') }, { term = true })
env.screen:expect([[
- ^ready $ |
- [Process exited 0] |
- |*4
+ ^ready $ |
+ [Process exited 0] |
+ |*5
]])
env.buf = api.nvim_get_current_buf()
api.nvim_set_option_value('modified', false, { buf = env.buf })
end)
- test_open_term_in_buf_with_closed_term(env)
+ test_enew_in_buf_with_finished_term(env)
+ test_open_term_in_buf_with_finished_term(env)
end)
describe('with closed nvim_open_term() channel', function()
local env = {}
before_each(function()
- env.screen = Screen.new(60, 6)
+ env.screen = Screen.new(50, 7)
local chan = api.nvim_open_term(0, {})
api.nvim_chan_send(chan, 'TEST')
fn.chanclose(chan)
env.screen:expect([[
- ^TEST |
- [Terminal closed] |
- |*4
+ ^TEST |
+ [Terminal closed] |
+ |*5
]])
env.buf = api.nvim_get_current_buf()
api.nvim_set_option_value('modified', false, { buf = env.buf })
end)
- test_open_term_in_buf_with_closed_term(env)
+ test_enew_in_buf_with_finished_term(env)
+ test_open_term_in_buf_with_finished_term(env)
end)
it('with nvim_open_term() channel and only 1 line is not reused by :enew', function()