From e6fae6445445fe266b353aa4ca4a4f40ad44f0ac Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 16 Feb 2026 21:47:45 +0800 Subject: [PATCH] fix(terminal): handle opening terminal on unloaded buffer (#37894) Problem: Strange behavior when opening terminal on unloaded buffer. Solution: For nvim_open_term() ensure the buffer is loaded as it needs to be read into the terminal. For jobstart() just open the memfile as the file content isn't needed. Not going to make nvim_open_term() pass stdin to the terminal when stdin isn't read into a buffer yet, as other APIs don't read stdin on unloaded buffer either. There are also other problems with loading buffer before reading stdin, so it's better to address those in another PR. --- src/nvim/api/buffer.c | 2 +- src/nvim/api/vim.c | 2 +- src/nvim/eval/funcs.c | 9 +++++ test/functional/core/startup_spec.lua | 55 +++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 9fd366333d..d0e1c890e1 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -49,7 +49,7 @@ #include "api/buffer.c.generated.h" /// Ensures that a buffer is loaded. -static buf_T *api_buf_ensure_loaded(Buffer buffer, Error *err) +buf_T *api_buf_ensure_loaded(Buffer buffer, Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index c753463699..29239f4481 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1106,7 +1106,7 @@ Integer nvim_open_term(Buffer buffer, Dict(open_term) *opts, Error *err) FUNC_API_SINCE(7) FUNC_API_TEXTLOCK_ALLOW_CMDWIN { - buf_T *buf = find_buffer_by_handle(buffer, err); + buf_T *buf = api_buf_ensure_loaded(buffer, err); if (!buf) { return 0; } diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index bdaf4d163d..4c333e593d 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -3611,6 +3611,15 @@ void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) const int pid = chan->stream.pty.proc.pid; buf_T *const buf = curbuf; + // If the buffer isn't loaded, open a memfile here to avoid spurious autocommands + // from open_buffer() when updating the terminal buffer later. + if (buf->b_ml.ml_mfp == NULL && ml_open(buf) == FAIL) { + // Internal error in ml_open(): stop the job. + proc_stop(&chan->stream.proc); + channel_decref(chan); + return; + } + channel_incref(chan); channel_terminal_alloc(buf, chan); diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index 3b6a14b3a4..490de454c6 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -936,6 +936,61 @@ describe('startup', function() | ]]) end) + + describe('opening a terminal before buffers are loaded #30765', function() + local lines = {} --- @type string[] + for i = 1, 50 do + lines[#lines + 1] = ('line%d'):format(i) + end + + setup(function() + write_file('Xsomefile', table.concat(lines, '\n') .. '\n') + end) + + teardown(function() + os.remove('Xsomefile') + end) + + it('sends buffer content to terminal with nvim_open_term()', function() + clear({ + args_rm = { '--headless' }, + args = { + 'Xsomefile', + '--cmd', + 'let g:chan = nvim_open_term(0, {}) | startinsert', + '--cmd', + 'call chansend(g:chan, "new_line1\nnew_line2\nnew_line3")', + }, + }) + local screen = Screen.new(50, 7) + screen:expect([[ + line48 | + line49 | + line50 | + new_line1 | + new_line2 | + new_line3^ | + {5:-- TERMINAL --} | + ]]) + eq(lines, api.nvim_buf_get_lines(0, 0, #lines, true)) + end) + + it('does not error with jobstart(…,{term=true})', function() + clear({ + args_rm = { '--headless' }, + args = { + 'Xsomefile', + '--cmd', + ('lua vim.fn.jobstart({%q}, {term = true})'):format(n.testprg('tty-test')), + }, + }) + local screen = Screen.new(50, 7) + screen:expect([[ + ^tty ready | + |*6 + ]]) + end) + end) end) describe('startup', function()