From 3621af9b970c80d2a6ff36569d7495391599c334 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 3 Jan 2026 17:55:07 +0800 Subject: [PATCH] fix(terminal): inconsistent mode change when switching buffer (#37215) Problem: When switching to another terminal buffer in Terminal mode, usually Nvim stays in Terminal mode, but leaves Terminal mode if the old terminal buffer was deleted. Solution: Don't always leave Terminal mode when active terminal buffer is deleted. Instead let terminal_check_focus() decide that. --- src/nvim/terminal.c | 10 +++++-- test/functional/terminal/ex_terminal_spec.lua | 30 ++++++++++++++++++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index ebacc3224c..3ccd2bfa7d 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -876,6 +876,11 @@ static bool terminal_check_focus(TerminalState *const s) if (s->term != curbuf->terminal) { // Active terminal buffer changed, flush terminal's cursor state to the UI. terminal_focus(s->term, false); + if (s->close) { + s->term->destroy = true; + s->term->opts.close_cb(s->term->opts.data); + s->close = false; + } s->term = curbuf->terminal; s->term->pending.cursor = true; @@ -894,6 +899,9 @@ static int terminal_check(VimState *state) { TerminalState *const s = (TerminalState *)state; + // Shouldn't reach here when pressing a key to close the terminal buffer. + assert(!s->close || (s->term->buf_handle == 0 && s->term != curbuf->terminal)); + if (stop_insert_mode || !terminal_check_focus(s)) { return 0; } @@ -913,7 +921,6 @@ static int terminal_check(VimState *state) s->term->refcount--; if (s->term->buf_handle == 0) { s->close = true; - return 0; } // Autocommands above may have changed focus, scrolled, or moved the cursor. @@ -986,7 +993,6 @@ static int terminal_execute(VimState *state, int key) s->term->refcount--; if (s->term->buf_handle == 0) { s->close = true; - return 0; } break; diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index bd63dbe2c1..587b96bcd8 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -4,11 +4,12 @@ local Screen = require('test.functional.ui.screen') local assert_alive = n.assert_alive local clear, poke_eventloop = n.clear, n.poke_eventloop -local testprg, source, eq = n.testprg, n.source, t.eq +local testprg, source, eq, neq = n.testprg, n.source, t.eq, t.neq local feed = n.feed local feed_command, eval = n.feed_command, n.eval local fn = n.fn local api = n.api +local exec_lua = n.exec_lua local retry = t.retry local ok = t.ok local command = n.command @@ -156,6 +157,33 @@ describe(':terminal', function() feed('') -- Add input to separate two RPC requests eq({ blocking = false, mode = 'nt' }, api.nvim_get_mode()) end) + + it('switching to another terminal buffer in Terminal mode', function() + command('terminal') + local buf0 = api.nvim_get_current_buf() + command('terminal') + local buf1 = api.nvim_get_current_buf() + command('terminal') + local buf2 = api.nvim_get_current_buf() + neq(buf0, buf1) + neq(buf0, buf2) + neq(buf1, buf2) + feed('i') + eq({ blocking = false, mode = 't' }, api.nvim_get_mode()) + api.nvim_set_current_buf(buf1) + eq({ blocking = false, mode = 't' }, api.nvim_get_mode()) + api.nvim_set_current_buf(buf0) + eq({ blocking = false, mode = 't' }, api.nvim_get_mode()) + exec_lua(function() + vim.api.nvim_set_current_buf(buf1) + vim.api.nvim_buf_delete(buf0, { force = true }) + end) + eq({ blocking = false, mode = 't' }, api.nvim_get_mode()) + api.nvim_set_current_buf(buf2) + eq({ blocking = false, mode = 't' }, api.nvim_get_mode()) + api.nvim_set_current_buf(buf1) + eq({ blocking = false, mode = 't' }, api.nvim_get_mode()) + end) end) local function test_terminal_with_fake_shell(backslash)