neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

commit e6fae6445445fe266b353aa4ca4a4f40ad44f0ac
parent 9c3ba551288a9f05d489dfee1077bab7261644be
Author: zeertzjq <zeertzjq@outlook.com>
Date:   Mon, 16 Feb 2026 21:47:45 +0800

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.
Diffstat:
Msrc/nvim/api/buffer.c | 2+-
Msrc/nvim/api/vim.c | 2+-
Msrc/nvim/eval/funcs.c | 9+++++++++
Mtest/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 @@ -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 @@ -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 @@ -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 @@ -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()