diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 9adc35d695..4877586189 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -2150,13 +2150,24 @@ static void refresh_terminal(Terminal *term) } linenr_T ml_before = buf->b_ml.ml_line_count; - refresh_size(term, buf); + bool resized = refresh_size(term, buf); refresh_scrollback(term, buf); refresh_screen(term, buf); int ml_added = buf->b_ml.ml_line_count - ml_before; adjust_topline_cursor(term, buf, ml_added); + // Resized window may have scrolled horizontally to keep its cursor in-view using the old terminal + // size. Reset the scroll, and let curs_columns correct it if that sends the cursor out-of-view. + if (resized) { + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer == buf && wp->w_leftcol != 0) { + wp->w_leftcol = 0; + curs_columns(wp, true); + } + } + } + // Copy pending events back to the main event queue multiqueue_move_events(main_loop.events, term->pending.events); } @@ -2227,10 +2238,10 @@ static void refresh_timer_cb(TimeWatcher *watcher, void *data) unblock_autocmds(); } -static void refresh_size(Terminal *term, buf_T *buf) +static bool refresh_size(Terminal *term, buf_T *buf) { if (!term->pending.resize || term->closed) { - return; + return false; } term->pending.resize = false; @@ -2239,6 +2250,7 @@ static void refresh_size(Terminal *term, buf_T *buf) term->invalid_start = 0; term->invalid_end = height; term->opts.resize_cb((uint16_t)width, (uint16_t)height, term->opts.data); + return true; } void on_scrollback_option_changed(Terminal *term) diff --git a/test/functional/terminal/window_spec.lua b/test/functional/terminal/window_spec.lua index e4342383b6..dc622f89ce 100644 --- a/test/functional/terminal/window_spec.lua +++ b/test/functional/terminal/window_spec.lua @@ -14,6 +14,7 @@ local eq = t.eq local eval = n.eval local skip = t.skip local is_os = t.is_os +local testprg = n.testprg describe(':terminal window', function() before_each(clear) @@ -33,6 +34,46 @@ describe(':terminal window', function() eq({ 1, 1, 1 }, eval('[&l:wrap, &wrap, &g:wrap]')) eq({ 1, 1, 1 }, eval('[&l:list, &list, &g:list]')) end) + + it('resets horizontal scroll on resize #35331', function() + local screen = tt.setup_screen(0, { testprg('shell-test'), 'INTERACT' }) + command('set statusline=%{win_getid()} splitright') + screen:expect([[ + interact $ ^ | + |*5 + {3:-- TERMINAL --} | + ]]) + feed_data(('A'):rep(30)) + screen:expect([[ + interact $ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA^ | + |*5 + {3:-- TERMINAL --} | + ]]) + command('vnew | wincmd p') + screen:expect([[ + interact $ AAAAAAAAAAAAA│ | + AAAAAAAAAAAAAAAAA^ │{4:~ }| + │{4:~ }|*3 + {17:1000 }{1:1001 }| + {3:-- TERMINAL --} | + ]]) + + feed([[o]]) + screen:expect([[ + interact $ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | + ^ | + |*5 + ]]) + -- Window with less room scrolls anyway to keep its cursor in-view. + feed('gg$20v') + screen:expect([[ + interact $ AAAAAAAAAAAAAAAAAA│$ AAAAAAAAAAAAAAAAA^A| + AAAAAAAAAAAA │AAA | + │ |*3 + {18:1000 }{17:1002 }| + | + ]]) + end) end) describe(':terminal window', function()