mirror of
https://github.com/neovim/neovim.git
synced 2025-09-07 11:58:17 +00:00
fix(terminal): avoid events messing up topline of focused terminal
Problem: topline of a focused terminal window may not tail to terminal output if events scroll the window. Solution: set the topline in terminal_check_cursor.
This commit is contained in:
@@ -795,6 +795,11 @@ static void terminal_check_cursor(void)
|
|||||||
curwin->w_wcol = term->cursor.col + win_col_off(curwin);
|
curwin->w_wcol = term->cursor.col + win_col_off(curwin);
|
||||||
curwin->w_cursor.lnum = MIN(curbuf->b_ml.ml_line_count,
|
curwin->w_cursor.lnum = MIN(curbuf->b_ml.ml_line_count,
|
||||||
row_to_linenr(term, term->cursor.row));
|
row_to_linenr(term, term->cursor.row));
|
||||||
|
const linenr_T topline = MAX(curbuf->b_ml.ml_line_count - curwin->w_view_height + 1, 1);
|
||||||
|
// Don't update topline if unchanged to avoid unnecessary redraws.
|
||||||
|
if (topline != curwin->w_topline) {
|
||||||
|
set_topline(curwin, topline);
|
||||||
|
}
|
||||||
// Nudge cursor when returning to normal-mode.
|
// Nudge cursor when returning to normal-mode.
|
||||||
int off = is_focused(term) ? 0 : (curwin->w_p_rl ? 1 : -1);
|
int off = is_focused(term) ? 0 : (curwin->w_p_rl ? 1 : -1);
|
||||||
coladvance(curwin, MAX(0, term->cursor.col + off));
|
coladvance(curwin, MAX(0, term->cursor.col + off));
|
||||||
@@ -2247,11 +2252,16 @@ static void adjust_topline(Terminal *term, buf_T *buf, int added)
|
|||||||
{
|
{
|
||||||
FOR_ALL_TAB_WINDOWS(tp, wp) {
|
FOR_ALL_TAB_WINDOWS(tp, wp) {
|
||||||
if (wp->w_buffer == buf) {
|
if (wp->w_buffer == buf) {
|
||||||
|
if (wp == curwin && is_focused(term)) {
|
||||||
|
// Move window cursor to terminal cursor's position and "follow" output.
|
||||||
|
terminal_check_cursor();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
linenr_T ml_end = buf->b_ml.ml_line_count;
|
linenr_T ml_end = buf->b_ml.ml_line_count;
|
||||||
bool following = ml_end == wp->w_cursor.lnum + added; // cursor at end?
|
bool following = ml_end == wp->w_cursor.lnum + added; // cursor at end?
|
||||||
bool focused = wp == curwin && is_focused(term);
|
|
||||||
|
|
||||||
if (following || focused) {
|
if (following) {
|
||||||
// "Follow" the terminal output
|
// "Follow" the terminal output
|
||||||
wp->w_cursor.lnum = ml_end;
|
wp->w_cursor.lnum = ml_end;
|
||||||
set_topline(wp, MAX(wp->w_cursor.lnum - wp->w_view_height + 1, 1));
|
set_topline(wp, MAX(wp->w_cursor.lnum - wp->w_view_height + 1, 1));
|
||||||
@@ -2259,14 +2269,10 @@ static void adjust_topline(Terminal *term, buf_T *buf, int added)
|
|||||||
// Ensure valid cursor for each window displaying this terminal.
|
// Ensure valid cursor for each window displaying this terminal.
|
||||||
wp->w_cursor.lnum = MIN(wp->w_cursor.lnum, ml_end);
|
wp->w_cursor.lnum = MIN(wp->w_cursor.lnum, ml_end);
|
||||||
}
|
}
|
||||||
if (focused) {
|
|
||||||
terminal_check_cursor();
|
|
||||||
} else {
|
|
||||||
mb_check_adjust_col(wp);
|
mb_check_adjust_col(wp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static int row_to_linenr(Terminal *term, int row)
|
static int row_to_linenr(Terminal *term, int row)
|
||||||
{
|
{
|
||||||
|
@@ -3,8 +3,10 @@ local n = require('test.functional.testnvim')()
|
|||||||
|
|
||||||
local tt = require('test.functional.testterm')
|
local tt = require('test.functional.testterm')
|
||||||
local feed_data = tt.feed_data
|
local feed_data = tt.feed_data
|
||||||
|
local feed_csi = tt.feed_csi
|
||||||
local feed, clear = n.feed, n.clear
|
local feed, clear = n.feed, n.clear
|
||||||
local poke_eventloop = n.poke_eventloop
|
local poke_eventloop = n.poke_eventloop
|
||||||
|
local exec_lua = n.exec_lua
|
||||||
local command = n.command
|
local command = n.command
|
||||||
local retry = t.retry
|
local retry = t.retry
|
||||||
local eq = t.eq
|
local eq = t.eq
|
||||||
@@ -332,6 +334,86 @@ describe(':terminal window', function()
|
|||||||
command('echo ""')
|
command('echo ""')
|
||||||
screen:expect_unchanged()
|
screen:expect_unchanged()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('has correct topline if scrolled by events', function()
|
||||||
|
skip(is_os('win'), '#31587')
|
||||||
|
local lines = {}
|
||||||
|
for i = 1, 10 do
|
||||||
|
table.insert(lines, 'cool line ' .. i)
|
||||||
|
end
|
||||||
|
feed_data(lines)
|
||||||
|
feed_csi('1;1H') -- Cursor to 1,1 (after any scrollback)
|
||||||
|
|
||||||
|
-- :sleep (with leeway) until the refresh_terminal uv timer event triggers before we move the
|
||||||
|
-- cursor. Check that the next terminal_check tails topline correctly.
|
||||||
|
command('set ruler | sleep 20m | call nvim_win_set_cursor(0, [1, 0])')
|
||||||
|
screen:expect([[
|
||||||
|
^cool line 5 |
|
||||||
|
cool line 6 |
|
||||||
|
cool line 7 |
|
||||||
|
cool line 8 |
|
||||||
|
cool line 9 |
|
||||||
|
cool line 10 |
|
||||||
|
{5:-- TERMINAL --} 6,1 Bot |
|
||||||
|
]])
|
||||||
|
command('call nvim_win_set_cursor(0, [1, 0])')
|
||||||
|
screen:expect_unchanged()
|
||||||
|
|
||||||
|
feed_csi('2;5H') -- Cursor to 2,5 (after any scrollback)
|
||||||
|
screen:expect([[
|
||||||
|
cool line 5 |
|
||||||
|
cool^ line 6 |
|
||||||
|
cool line 7 |
|
||||||
|
cool line 8 |
|
||||||
|
cool line 9 |
|
||||||
|
cool line 10 |
|
||||||
|
{5:-- TERMINAL --} 7,5 Bot |
|
||||||
|
]])
|
||||||
|
-- Check topline correct after leaving terminal mode.
|
||||||
|
-- The new cursor position is one column left of the terminal's actual cursor position.
|
||||||
|
command('stopinsert | call nvim_win_set_cursor(0, [1, 0])')
|
||||||
|
screen:expect([[
|
||||||
|
cool line 5 |
|
||||||
|
coo^l line 6 |
|
||||||
|
cool line 7 |
|
||||||
|
cool line 8 |
|
||||||
|
cool line 9 |
|
||||||
|
cool line 10 |
|
||||||
|
7,4 Bot |
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('not unnecessarily redrawn by events', function()
|
||||||
|
eq('t', eval('mode()'))
|
||||||
|
exec_lua(function()
|
||||||
|
_G.redraws = {}
|
||||||
|
local ns = vim.api.nvim_create_namespace('test')
|
||||||
|
vim.api.nvim_set_decoration_provider(ns, {
|
||||||
|
on_start = function()
|
||||||
|
table.insert(_G.redraws, 'start')
|
||||||
|
end,
|
||||||
|
on_win = function(_, win)
|
||||||
|
table.insert(_G.redraws, 'win ' .. win)
|
||||||
|
end,
|
||||||
|
on_end = function()
|
||||||
|
table.insert(_G.redraws, 'end')
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
-- Setting a decoration provider typically causes an initial redraw.
|
||||||
|
vim.cmd.redraw()
|
||||||
|
_G.redraws = {}
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- The event we sent above to set up the test shouldn't have caused a redraw.
|
||||||
|
-- For good measure, also poke the event loop.
|
||||||
|
poke_eventloop()
|
||||||
|
eq({}, exec_lua('return _G.redraws'))
|
||||||
|
|
||||||
|
-- Redraws if we do something useful, of course.
|
||||||
|
feed_data('foo')
|
||||||
|
screen:expect { any = 'foo' }
|
||||||
|
eq({ 'start', 'win 1000', 'end' }, exec_lua('return _G.redraws'))
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe(':terminal with multigrid', function()
|
describe(':terminal with multigrid', function()
|
||||||
|
Reference in New Issue
Block a user