mirror of
https://github.com/neovim/neovim.git
synced 2025-11-15 23:01:24 +00:00
fix(terminal): keep last cursor if it's on the last row
This commit is contained in:
@@ -1246,7 +1246,8 @@ void mark_adjust_buf(buf_T *buf, linenr_T line1, linenr_T line2, linenr_T amount
|
||||
ONE_ADJUST(&(buf->b_last_change.mark.lnum));
|
||||
|
||||
// last cursor position, if it was set
|
||||
if (!equalpos(buf->b_last_cursor.mark, initpos)) {
|
||||
if (!equalpos(buf->b_last_cursor.mark, initpos)
|
||||
&& (!by_term || buf->b_last_cursor.mark.lnum < buf->b_ml.ml_line_count)) {
|
||||
ONE_ADJUST(&(buf->b_last_cursor.mark.lnum));
|
||||
}
|
||||
|
||||
@@ -1364,9 +1365,11 @@ void mark_adjust_buf(buf_T *buf, linenr_T line1, linenr_T line2, linenr_T amount
|
||||
// adjust per-window "last cursor" positions
|
||||
for (size_t i = 0; i < kv_size(buf->b_wininfo); i++) {
|
||||
WinInfo *wip = kv_A(buf->b_wininfo, i);
|
||||
if (!by_term || wip->wi_mark.mark.lnum < buf->b_ml.ml_line_count) {
|
||||
ONE_ADJUST_CURSOR(&(wip->wi_mark.mark));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This code is used often, needs to be fast.
|
||||
#define COL_ADJUST(pp) \
|
||||
|
||||
@@ -773,7 +773,7 @@ bool terminal_enter(void)
|
||||
set_terminal_winopts(s);
|
||||
|
||||
s->term->pending.cursor = true; // Update the cursor shape table
|
||||
adjust_topline(s->term, buf, 0); // scroll to end
|
||||
adjust_topline_cursor(s->term, buf, 0); // scroll to end
|
||||
showmode();
|
||||
ui_cursor_shape();
|
||||
|
||||
@@ -2108,7 +2108,7 @@ static void refresh_terminal(Terminal *term)
|
||||
refresh_screen(term, buf);
|
||||
|
||||
int ml_added = buf->b_ml.ml_line_count - ml_before;
|
||||
adjust_topline(term, buf, ml_added);
|
||||
adjust_topline_cursor(term, buf, ml_added);
|
||||
|
||||
// Copy pending events back to the main event queue
|
||||
multiqueue_move_events(main_loop.events, term->pending.events);
|
||||
@@ -2324,8 +2324,10 @@ static void refresh_screen(Terminal *term, buf_T *buf)
|
||||
term->invalid_end = -1;
|
||||
}
|
||||
|
||||
static void adjust_topline(Terminal *term, buf_T *buf, int added)
|
||||
static void adjust_topline_cursor(Terminal *term, buf_T *buf, int added)
|
||||
{
|
||||
linenr_T ml_end = buf->b_ml.ml_line_count;
|
||||
|
||||
FOR_ALL_TAB_WINDOWS(tp, wp) {
|
||||
if (wp->w_buffer == buf) {
|
||||
if (wp == curwin && is_focused(term)) {
|
||||
@@ -2334,9 +2336,7 @@ static void adjust_topline(Terminal *term, buf_T *buf, int added)
|
||||
continue;
|
||||
}
|
||||
|
||||
linenr_T ml_end = buf->b_ml.ml_line_count;
|
||||
bool following = ml_end == wp->w_cursor.lnum + added; // cursor at end?
|
||||
|
||||
if (following) {
|
||||
// "Follow" the terminal output
|
||||
wp->w_cursor.lnum = ml_end;
|
||||
@@ -2348,6 +2348,17 @@ static void adjust_topline(Terminal *term, buf_T *buf, int added)
|
||||
mb_check_adjust_col(wp);
|
||||
}
|
||||
}
|
||||
|
||||
if (ml_end == buf->b_last_cursor.mark.lnum + added) {
|
||||
buf->b_last_cursor.mark.lnum = ml_end;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < kv_size(buf->b_wininfo); i++) {
|
||||
WinInfo *wip = kv_A(buf->b_wininfo, i);
|
||||
if (ml_end == wip->wi_mark.mark.lnum + added) {
|
||||
wip->wi_mark.mark.lnum = ml_end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int row_to_linenr(Terminal *term, int row)
|
||||
|
||||
@@ -3,7 +3,7 @@ local n = require('test.functional.testnvim')()
|
||||
local Screen = require('test.functional.ui.screen')
|
||||
local tt = require('test.functional.testterm')
|
||||
|
||||
local clear, eq = n.clear, t.eq
|
||||
local clear, eq, neq = n.clear, t.eq, t.neq
|
||||
local feed, testprg = n.feed, n.testprg
|
||||
local fn = n.fn
|
||||
local eval = n.eval
|
||||
@@ -18,34 +18,74 @@ local assert_alive = n.assert_alive
|
||||
local skip = t.skip
|
||||
local is_os = t.is_os
|
||||
|
||||
describe(':terminal scrollback', function()
|
||||
local screen
|
||||
local function test_terminal_scrollback(hide_curbuf)
|
||||
local screen --- @type test.functional.ui.screen
|
||||
local buf --- @type integer
|
||||
local chan --- @type integer
|
||||
local otherbuf --- @type integer
|
||||
local restore_terminal_mode --- @type boolean?
|
||||
|
||||
local function may_hide_curbuf()
|
||||
if hide_curbuf then
|
||||
eq(nil, restore_terminal_mode)
|
||||
restore_terminal_mode = vim.startswith(api.nvim_get_mode().mode, 't')
|
||||
api.nvim_set_current_buf(otherbuf)
|
||||
end
|
||||
end
|
||||
|
||||
local function may_restore_curbuf()
|
||||
if hide_curbuf then
|
||||
neq(nil, restore_terminal_mode)
|
||||
eq(buf, fn.bufnr('#'))
|
||||
feed('<C-^>') -- "view" in 'jumpoptions' applies to this
|
||||
if restore_terminal_mode then
|
||||
feed('i')
|
||||
else
|
||||
-- Cursor position was restored from wi_mark, not b_last_cursor.
|
||||
-- Check that b_last_cursor and wi_mark are the same.
|
||||
local last_cursor = fn.getpos([['"]])
|
||||
local restored_cursor = fn.getpos('.')
|
||||
if last_cursor[2] > 0 then
|
||||
eq(restored_cursor, last_cursor)
|
||||
else
|
||||
eq({ 0, 0, 0, 0 }, last_cursor)
|
||||
eq({ 0, 1, 1, 0 }, restored_cursor)
|
||||
end
|
||||
end
|
||||
restore_terminal_mode = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- @param prefix string
|
||||
--- @param start integer
|
||||
--- @param stop integer
|
||||
local function feed_lines(prefix, start, stop)
|
||||
may_hide_curbuf()
|
||||
local data = ''
|
||||
for i = start, stop do
|
||||
data = data .. prefix .. tostring(i) .. '\n'
|
||||
end
|
||||
api.nvim_chan_send(chan, data)
|
||||
retry(nil, 1000, function()
|
||||
eq({ prefix .. tostring(stop), '' }, api.nvim_buf_get_lines(buf, -3, -1, true))
|
||||
end)
|
||||
may_restore_curbuf()
|
||||
end
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
command('set nostartofline jumpoptions+=view')
|
||||
screen = tt.setup_screen(nil, nil, 30)
|
||||
end)
|
||||
|
||||
local function feed_new_lines_and_wait(count)
|
||||
local lines = {}
|
||||
for i = 1, count do
|
||||
table.insert(lines, 'new_line' .. tostring(i))
|
||||
buf = api.nvim_get_current_buf()
|
||||
chan = api.nvim_get_option_value('channel', { buf = buf })
|
||||
if hide_curbuf then
|
||||
otherbuf = api.nvim_create_buf(true, false)
|
||||
end
|
||||
table.insert(lines, '')
|
||||
feed_data(lines)
|
||||
retry(nil, 1000, function()
|
||||
eq({ 'new_line' .. tostring(count), '' }, api.nvim_buf_get_lines(0, -3, -1, true))
|
||||
end)
|
||||
end
|
||||
|
||||
describe('when the limit is exceeded', function()
|
||||
before_each(function()
|
||||
local lines = {}
|
||||
for i = 1, 30 do
|
||||
table.insert(lines, 'line' .. tostring(i))
|
||||
end
|
||||
table.insert(lines, '')
|
||||
feed_data(lines)
|
||||
feed_lines('line', 1, 30)
|
||||
screen:expect([[
|
||||
line26 |
|
||||
line27 |
|
||||
@@ -87,7 +127,7 @@ describe(':terminal scrollback', function()
|
||||
end)
|
||||
|
||||
it("when outputting fewer than 'scrollback' lines", function()
|
||||
feed_new_lines_and_wait(6)
|
||||
feed_lines('new_line', 1, 6)
|
||||
screen:expect([[
|
||||
line26 |
|
||||
line27 |
|
||||
@@ -102,7 +142,7 @@ describe(':terminal scrollback', function()
|
||||
end)
|
||||
|
||||
it("when outputting more than 'scrollback' lines", function()
|
||||
feed_new_lines_and_wait(11)
|
||||
feed_lines('new_line', 1, 11)
|
||||
screen:expect([[
|
||||
line27 |
|
||||
{101:line2^8} |
|
||||
@@ -117,7 +157,7 @@ describe(':terminal scrollback', function()
|
||||
end)
|
||||
|
||||
it('when outputting more lines than whole buffer', function()
|
||||
feed_new_lines_and_wait(20)
|
||||
feed_lines('new_line', 1, 20)
|
||||
screen:expect([[
|
||||
^new_line6 |
|
||||
new_line7 |
|
||||
@@ -150,14 +190,14 @@ describe(':terminal scrollback', function()
|
||||
end)
|
||||
|
||||
it("when outputting fewer than 'scrollback' lines", function()
|
||||
feed_new_lines_and_wait(6)
|
||||
screen:expect_unchanged()
|
||||
feed_lines('new_line', 1, 6)
|
||||
screen:expect_unchanged(hide_curbuf)
|
||||
eq({ 0, 4, 4, 0 }, fn.getpos("'m"))
|
||||
eq({ 0, 4, 6, 0 }, fn.getpos('.'))
|
||||
end)
|
||||
|
||||
it("when outputting more than 'scrollback' lines", function()
|
||||
feed_new_lines_and_wait(11)
|
||||
feed_lines('new_line', 1, 11)
|
||||
screen:expect([[
|
||||
^line27 |
|
||||
line28 |
|
||||
@@ -175,7 +215,7 @@ describe(':terminal scrollback', function()
|
||||
|
||||
describe('with cursor at last row', function()
|
||||
before_each(function()
|
||||
feed_data({ 'line1', 'line2', 'line3', 'line4', '' })
|
||||
feed_lines('line', 1, 4)
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
line1 |
|
||||
@@ -201,7 +241,7 @@ describe(':terminal scrollback', function()
|
||||
|
||||
it("when outputting more than 'scrollback' lines in Normal mode", function()
|
||||
feed([[<C-\><C-N>]])
|
||||
feed_new_lines_and_wait(11)
|
||||
feed_lines('new_line', 1, 11)
|
||||
screen:expect([[
|
||||
new_line7 |
|
||||
new_line8 |
|
||||
@@ -222,11 +262,33 @@ describe(':terminal scrollback', function()
|
||||
|
|
||||
]])
|
||||
eq({ 0, 2, 4, 0 }, fn.getpos("'m"))
|
||||
feed('G')
|
||||
feed_lines('new_line', 12, 31)
|
||||
screen:expect([[
|
||||
new_line27 |
|
||||
new_line28 |
|
||||
new_line29 |
|
||||
new_line30 |
|
||||
new_line31 |
|
||||
^ |
|
||||
|
|
||||
]])
|
||||
feed('gg')
|
||||
screen:expect([[
|
||||
^new_line17 |
|
||||
new_line18 |
|
||||
new_line19 |
|
||||
new_line20 |
|
||||
new_line21 |
|
||||
new_line22 |
|
||||
|
|
||||
]])
|
||||
eq({ 0, 0, 0, 0 }, fn.getpos("'m"))
|
||||
end)
|
||||
|
||||
describe('and 1 line is printed', function()
|
||||
before_each(function()
|
||||
feed_data({ 'line5', '' })
|
||||
feed_lines('line', 5, 5)
|
||||
end)
|
||||
|
||||
it('will hide the top line', function()
|
||||
@@ -245,7 +307,7 @@ describe(':terminal scrollback', function()
|
||||
|
||||
describe('and then 3 more lines are printed', function()
|
||||
before_each(function()
|
||||
feed_data({ 'line6', 'line7', 'line8', '' })
|
||||
feed_lines('line', 6, 8)
|
||||
end)
|
||||
|
||||
it('will hide the top 4 lines', function()
|
||||
@@ -299,7 +361,9 @@ describe(':terminal scrollback', function()
|
||||
describe('and height decreased by 1', function()
|
||||
local function will_hide_top_line()
|
||||
feed([[<C-\><C-N>]])
|
||||
may_hide_curbuf()
|
||||
screen:try_resize(screen._width - 2, screen._height - 1)
|
||||
may_restore_curbuf()
|
||||
screen:expect([[
|
||||
{101:line2} |
|
||||
line3 |
|
||||
@@ -316,7 +380,9 @@ describe(':terminal scrollback', function()
|
||||
describe('and then decreased by 2', function()
|
||||
before_each(function()
|
||||
will_hide_top_line()
|
||||
may_hide_curbuf()
|
||||
screen:try_resize(screen._width - 2, screen._height - 2)
|
||||
may_restore_curbuf()
|
||||
end)
|
||||
|
||||
it('will hide the top 3 lines', function()
|
||||
@@ -357,7 +423,9 @@ describe(':terminal scrollback', function()
|
||||
|
||||
describe('and the height is decreased by 2', function()
|
||||
before_each(function()
|
||||
may_hide_curbuf()
|
||||
screen:try_resize(screen._width, screen._height - 2)
|
||||
may_restore_curbuf()
|
||||
end)
|
||||
|
||||
local function will_delete_last_two_lines()
|
||||
@@ -376,7 +444,9 @@ describe(':terminal scrollback', function()
|
||||
describe('and then decreased by 1', function()
|
||||
before_each(function()
|
||||
will_delete_last_two_lines()
|
||||
may_hide_curbuf()
|
||||
screen:try_resize(screen._width, screen._height - 1)
|
||||
may_restore_curbuf()
|
||||
end)
|
||||
|
||||
it('will delete the last line and hide the first', function()
|
||||
@@ -408,7 +478,7 @@ describe(':terminal scrollback', function()
|
||||
|
||||
describe('with 4 lines hidden in the scrollback', function()
|
||||
before_each(function()
|
||||
feed_data({ 'line1', 'line2', 'line3', 'line4', '' })
|
||||
feed_lines('line', 1, 4)
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
line1 |
|
||||
@@ -430,7 +500,9 @@ describe(':terminal scrollback', function()
|
||||
^ |
|
||||
{5:-- TERMINAL --} |
|
||||
]])
|
||||
may_hide_curbuf()
|
||||
screen:try_resize(screen._width, screen._height - 3)
|
||||
may_restore_curbuf()
|
||||
screen:expect([[
|
||||
line4 |
|
||||
rows: 3, cols: 30 |
|
||||
@@ -448,7 +520,9 @@ describe(':terminal scrollback', function()
|
||||
return
|
||||
end
|
||||
local function pop_then_push()
|
||||
may_hide_curbuf()
|
||||
screen:try_resize(screen._width, screen._height + 1)
|
||||
may_restore_curbuf()
|
||||
screen:expect([[
|
||||
line4 |
|
||||
rows: 3, cols: 30 |
|
||||
@@ -465,7 +539,9 @@ describe(':terminal scrollback', function()
|
||||
before_each(function()
|
||||
pop_then_push()
|
||||
eq(8, api.nvim_buf_line_count(0))
|
||||
may_hide_curbuf()
|
||||
screen:try_resize(screen._width, screen._height + 3)
|
||||
may_restore_curbuf()
|
||||
end)
|
||||
|
||||
local function pop3_then_push1()
|
||||
@@ -500,7 +576,9 @@ describe(':terminal scrollback', function()
|
||||
before_each(function()
|
||||
pop3_then_push1()
|
||||
feed('Gi')
|
||||
may_hide_curbuf()
|
||||
screen:try_resize(screen._width, screen._height + 4)
|
||||
may_restore_curbuf()
|
||||
end)
|
||||
|
||||
it('will show all lines and leave a blank one at the end', function()
|
||||
@@ -527,6 +605,55 @@ describe(':terminal scrollback', function()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('reducing &scrollback deletes extra lines immediately', function()
|
||||
feed_lines('line', 1, 30)
|
||||
screen:expect([[
|
||||
line26 |
|
||||
line27 |
|
||||
line28 |
|
||||
line29 |
|
||||
line30 |
|
||||
^ |
|
||||
{5:-- TERMINAL --} |
|
||||
]])
|
||||
local term_height = 6 -- Actual terminal screen height, not the scrollback
|
||||
-- Initial
|
||||
local scrollback = api.nvim_get_option_value('scrollback', { buf = buf })
|
||||
eq(scrollback + term_height, fn.line('$'))
|
||||
eq(scrollback + term_height, fn.line('.'))
|
||||
n.fn.setpos("'m", { 0, scrollback + 1, 4, 0 })
|
||||
local ns = api.nvim_create_namespace('test')
|
||||
api.nvim_buf_set_extmark(0, ns, scrollback, 0, { end_col = 6, hl_group = 'ErrorMsg' })
|
||||
screen:expect([[
|
||||
{101:line26} |
|
||||
line27 |
|
||||
line28 |
|
||||
line29 |
|
||||
line30 |
|
||||
^ |
|
||||
{5:-- TERMINAL --} |
|
||||
]])
|
||||
-- Reduction
|
||||
scrollback = scrollback - 2
|
||||
may_hide_curbuf()
|
||||
api.nvim_set_option_value('scrollback', scrollback, { buf = buf })
|
||||
may_restore_curbuf()
|
||||
eq(scrollback + term_height, fn.line('$'))
|
||||
eq(scrollback + term_height, fn.line('.'))
|
||||
screen:expect_unchanged(hide_curbuf)
|
||||
eq({ 0, scrollback + 1, 4, 0 }, n.fn.getpos("'m"))
|
||||
end)
|
||||
end
|
||||
|
||||
describe(':terminal scrollback', function()
|
||||
describe('in current buffer', function()
|
||||
test_terminal_scrollback(false)
|
||||
end)
|
||||
|
||||
describe('in hidden buffer', function()
|
||||
test_terminal_scrollback(true)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe(':terminal prints more lines than the screen height and exits', function()
|
||||
@@ -652,48 +779,6 @@ describe("'scrollback' option", function()
|
||||
eq((is_os('win') and '27: line' or '26: line'), eval("getline(line('w0') - 10)->trim(' ', 2)"))
|
||||
end)
|
||||
|
||||
it('deletes extra lines immediately', function()
|
||||
-- Scrollback is 10 on setup_screen
|
||||
local screen = tt.setup_screen(nil, nil, 30)
|
||||
local lines = {}
|
||||
for i = 1, 30 do
|
||||
table.insert(lines, 'line' .. tostring(i))
|
||||
end
|
||||
table.insert(lines, '')
|
||||
feed_data(lines)
|
||||
screen:expect([[
|
||||
line26 |
|
||||
line27 |
|
||||
line28 |
|
||||
line29 |
|
||||
line30 |
|
||||
^ |
|
||||
{5:-- TERMINAL --} |
|
||||
]])
|
||||
local ns = api.nvim_create_namespace('test')
|
||||
local term_height = 6 -- Actual terminal screen height, not the scrollback
|
||||
-- Initial
|
||||
local scrollback = api.nvim_get_option_value('scrollback', {})
|
||||
eq(scrollback + term_height, fn.line('$'))
|
||||
n.fn.setpos("'m", { 0, scrollback + 1, 4, 0 })
|
||||
api.nvim_buf_set_extmark(0, ns, scrollback, 0, { end_col = 6, hl_group = 'ErrorMsg' })
|
||||
screen:expect([[
|
||||
{101:line26} |
|
||||
line27 |
|
||||
line28 |
|
||||
line29 |
|
||||
line30 |
|
||||
^ |
|
||||
{5:-- TERMINAL --} |
|
||||
]])
|
||||
-- Reduction
|
||||
scrollback = scrollback - 2
|
||||
api.nvim_set_option_value('scrollback', scrollback, {})
|
||||
eq(scrollback + term_height, fn.line('$'))
|
||||
screen:expect_unchanged()
|
||||
eq({ 0, scrollback + 1, 4, 0 }, n.fn.getpos("'m"))
|
||||
end)
|
||||
|
||||
it('defaults to 10000 in :terminal buffers', function()
|
||||
set_fake_shell()
|
||||
command('terminal')
|
||||
|
||||
Reference in New Issue
Block a user