From 238d4fa71ad92edb66be2806c161e6304fc7b839 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 20 Mar 2026 12:05:19 +0800 Subject: [PATCH] fix(terminal): possible missing refresh with buffer updates (#38388) Problem: Terminal refresh may be missed if buffer update callbacks poll for uv events. Example test failure on FreeBSD: FAILED test/functional/terminal/buffer_spec.lua @ 1049: :terminal buffer scrollback is correct if buffer update callbacks poll for uv events test/functional/terminal/buffer_spec.lua:1004: Row 1 did not match. Expected: |*19995: TEST{MATCH: +}| |*19996: TEST{MATCH: +}| |*19997: TEST{MATCH: +}| |*19998: TEST{MATCH: +}| |*19999: TEST{MATCH: +}| |^[Process exited 0] | |{5:-- TERMINAL --} | Actual: |*19696: TEST | |*19697: TEST | |*19698: TEST | |*19699: TEST | |*19700: TEST | |^[Process exited 0] | |{5:-- TERMINAL --} | Solution: Call changed_lines() after resetting invalid region in refresh_screen(). Handle terminal being invalidated in the middle of refresh_timer_cb(). --- src/nvim/terminal.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index cfe47c3d05..b52c21b5d0 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -2455,18 +2455,24 @@ static void refresh_timer_cb(TimeWatcher *watcher, void *data) if (exiting) { // Cannot redraw (requires event loop) during teardown/exit. return; } - Terminal *term; - void *stub; (void)(stub); - // don't process autocommands while updating terminal buffers + + // Don't process autocommands while updating terminal buffers. block_autocmds(); - set_foreach(&invalidated_terminals, term, { + // Refreshing one terminal may poll for output to another, which should not + // interfere with the set_foreach() below. + Set(ptr_t) to_refresh = invalidated_terminals; + invalidated_terminals = (Set(ptr_t)) SET_INIT; + + Terminal *term; + set_foreach(&to_refresh, term, { // Skip terminals in synchronized output — they will be refreshed // when the synchronized update ends (mode 2026 reset). if (!term->synchronized_output) { refresh_terminal(term); } }); - set_clear(ptr_t, &invalidated_terminals); + + set_destroy(ptr_t, &to_refresh); unblock_autocmds(); } @@ -2612,9 +2618,11 @@ static void refresh_screen(Terminal *term, buf_T *buf) int change_start = row_to_linenr(term, term->invalid_start); int change_end = change_start + changed; - changed_lines(buf, change_start, 0, change_end, added, true); term->invalid_start = INT_MAX; term->invalid_end = -1; + // Call this after resetting the invalid region, as buffer update callbacks may + // poll for terminal output and lead to new invalidations. + changed_lines(buf, change_start, 0, change_end, added, true); } static void adjust_topline_cursor(Terminal *term, buf_T *buf, int added)