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.
This commit is contained in:
zeertzjq
2026-02-16 21:47:45 +08:00
committed by GitHub
parent 9c3ba55128
commit e6fae64454
4 changed files with 66 additions and 2 deletions

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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()