From 67832710a5d32a19aaf1b2934c428f02816ea22e Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 24 Oct 2025 06:01:13 +0800 Subject: [PATCH] fix(terminal): wrong row in TermRequest with full scrollback (#36298) Problem: Wrong row in TermRequest with full scrollback. Solution: Subtract by the number of lines deleted from scrollback. --- runtime/doc/autocmd.txt | 3 +- runtime/lua/vim/_defaults.lua | 4 +- src/nvim/terminal.c | 11 +++- test/functional/terminal/buffer_spec.lua | 77 ++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 5 deletions(-) diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 7dd0962c49..180e59af8f 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -1066,7 +1066,8 @@ TermRequest When a |:terminal| child process emits an OSC, - sequence: the received sequence - cursor: (1,0)-indexed, buffer-relative position of the cursor when the sequence was - received + received (line number may be <= 0 if the + position is no longer in the buffer) This is triggered even when inside an autocommand defined without |autocmd-nested|. diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index f728bb9949..f664565461 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -580,7 +580,9 @@ do callback = function(args) if string.match(args.data.sequence, '^\027]133;A') then local lnum = args.data.cursor[1] ---@type integer - vim.api.nvim_buf_set_extmark(args.buf, nvim_terminal_prompt_ns, lnum - 1, 0, {}) + if lnum >= 1 then + vim.api.nvim_buf_set_extmark(args.buf, nvim_terminal_prompt_ns, lnum - 1, 0, {}) + end end end, }) diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 7fa799ef95..d2f1c72a9e 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -158,6 +158,7 @@ struct terminal { // it actually points to entries that are no longer in sb_buffer (because the // window height has increased) and must be deleted from the terminal buffer int sb_pending; + size_t sb_deleted; // Lines deleted from sb_buffer. char *title; // VTermStringFragment buffer size_t title_len; // number of rows pushed to sb_buffer @@ -230,6 +231,7 @@ static void emit_termrequest(void **argv) StringBuilder *pending_send = argv[3]; int row = (int)(intptr_t)argv[4]; int col = (int)(intptr_t)argv[5]; + size_t sb_deleted = (size_t)(intptr_t)argv[6]; if (term->sb_pending > 0) { // Don't emit the event while there is pending scrollback because we need @@ -237,14 +239,15 @@ static void emit_termrequest(void **argv) // the event onto the pending queue where it will be executed after the // terminal is refreshed and the pending scrollback is cleared. multiqueue_put(term->pending.events, emit_termrequest, term, sequence, (void *)sequence_length, - pending_send, (void *)(intptr_t)row, (void *)(intptr_t)col); + pending_send, (void *)(intptr_t)row, (void *)(intptr_t)col, + (void *)(intptr_t)sb_deleted); return; } set_vim_var_string(VV_TERMREQUEST, sequence, (ptrdiff_t)sequence_length); MAXSIZE_TEMP_ARRAY(cursor, 2); - ADD_C(cursor, INTEGER_OBJ(row)); + ADD_C(cursor, INTEGER_OBJ(row - (int64_t)(term->sb_deleted - sb_deleted))); ADD_C(cursor, INTEGER_OBJ(col)); MAXSIZE_TEMP_DICT(data, 2); @@ -278,7 +281,8 @@ static void schedule_termrequest(Terminal *term) multiqueue_put(main_loop.events, emit_termrequest, term, xmemdup(term->termrequest_buffer.items, term->termrequest_buffer.size), (void *)(intptr_t)term->termrequest_buffer.size, term->pending.send, - (void *)(intptr_t)line, (void *)(intptr_t)term->cursor.col); + (void *)(intptr_t)line, (void *)(intptr_t)term->cursor.col, + (void *)(intptr_t)term->sb_deleted); } static int parse_osc8(const char *str, int *attr) @@ -1451,6 +1455,7 @@ static int term_sb_push(int cols, const VTermScreenCell *cells, void *data) } else { xfree(term->sb_buffer[term->sb_current - 1]); } + term->sb_deleted++; // Make room at the start by shifting to the right. memmove(term->sb_buffer + 1, term->sb_buffer, diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index d7d4eb8f3f..24bf673db6 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -555,6 +555,83 @@ describe(':terminal buffer', function() {5:-- TERMINAL --} | ]]) eq({ 22, 6 }, exec_lua('return _G.cursor')) + + api.nvim_chan_send(term, '\nHello\027]133;D\027\\\nworld!\n') + screen:expect([[ + > |*4 + Hello | + world! | + Hello | + world! | + ^ | + {5:-- TERMINAL --} | + ]]) + eq({ 23, 5 }, exec_lua('return _G.cursor')) + + api.nvim_chan_send(term, 'Hello\027]133;D\027\\\nworld!' .. ('\n'):rep(6)) + screen:expect([[ + world! | + Hello | + world! | + |*5 + ^ | + {5:-- TERMINAL --} | + ]]) + eq({ 25, 5 }, exec_lua('return _G.cursor')) + + api.nvim_set_option_value('scrollback', 10, {}) + eq(19, api.nvim_buf_line_count(0)) + + api.nvim_chan_send(term, 'Hello\nworld!\027]133;D\027\\') + screen:expect([[ + Hello | + world! | + |*5 + Hello | + world!^ | + {5:-- TERMINAL --} | + ]]) + eq({ 19, 6 }, exec_lua('return _G.cursor')) + + api.nvim_chan_send(term, '\nHello\027]133;D\027\\\nworld!\n') + screen:expect([[ + |*4 + Hello | + world! | + Hello | + world! | + ^ | + {5:-- TERMINAL --} | + ]]) + eq({ 17, 5 }, exec_lua('return _G.cursor')) + + api.nvim_chan_send(term, 'Hello\027]133;D\027\\\nworld!' .. ('\n'):rep(6)) + screen:expect([[ + world! | + Hello | + world! | + |*5 + ^ | + {5:-- TERMINAL --} | + ]]) + eq({ 12, 5 }, exec_lua('return _G.cursor')) + + api.nvim_chan_send(term, 'Hello\027]133;D\027\\\nworld!' .. ('\n'):rep(8)) + screen:expect([[ + world! | + |*7 + ^ | + {5:-- TERMINAL --} | + ]]) + eq({ 10, 5 }, exec_lua('return _G.cursor')) + + api.nvim_chan_send(term, 'Hello\027]133;D\027\\\nworld!' .. ('\n'):rep(20)) + screen:expect([[ + |*8 + ^ | + {5:-- TERMINAL --} | + ]]) + eq({ -2, 5 }, exec_lua('return _G.cursor')) end) it('does not cause hang in vim.wait() #32753', function()