diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 01beaf0820..fb62202ac3 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -1350,8 +1350,11 @@ static int term_movecursor(VTermPos new_pos, VTermPos old_pos, int visible, void } static void buf_set_term_title(buf_T *buf, const char *title, size_t len) - FUNC_ATTR_NONNULL_ALL { + if (!buf) { + return; // In case of receiving OSC 2 between buffer close and job exit. + } + Error err = ERROR_INIT; dict_set_var(buf->b_vars, STATIC_CSTR_AS_STRING("term_title"), @@ -1378,7 +1381,7 @@ static int term_settermprop(VTermProp prop, VTermValue *val, void *data) break; case VTERM_PROP_TITLE: { - buf_T *buf = handle_get_buffer(term->buf_handle); + buf_T *buf = handle_get_buffer(term->buf_handle); // May be NULL VTermStringFragment frag = val->string; if (frag.initial && frag.final) { diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index 627103c655..48b2fcc601 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -431,6 +431,36 @@ describe(':terminal buffer', function() ]]) eq('TermLeave bar false', exec_lua('return _G.last_event')) end) + + it('no crash with race between buffer close and OSC 2', function() + skip(is_os('win'), 'tty-test cannot forward OSC 2 on Windows?') + exec_lua(function() + vim.api.nvim_chan_send(vim.bo.channel, '\027]2;SOME_TITLE\007') + end) + retry(nil, 4000, function() + eq('SOME_TITLE', api.nvim_buf_get_var(0, 'term_title')) + end) + screen:expect_unchanged() + --- @type string + local title_before_del = exec_lua(function() + vim.wait(10) -- Ensure there are no pending events. + vim.api.nvim_chan_send(vim.bo.channel, '\027]2;OTHER_TITLE\007') + vim.uv.run('once') -- Only process the pending write. + vim.uv.sleep(50) -- Block the event loop and wait for tty-test to forward OSC 2. + local term_title = vim.b.term_title + vim.api.nvim_buf_delete(0, { force = true }) + vim.wait(10, nil, nil, true) -- Process fast events only. + return term_title + end) + -- Title isn't changed until the second vim.wait(). + eq('SOME_TITLE', title_before_del) + screen:expect([[ + ^ | + {4:~ }|*5 + | + ]]) + assert_alive() + end) end) describe(':terminal buffer', function()