aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/autocmd.txt24
-rw-r--r--runtime/doc/news.txt5
-rw-r--r--runtime/doc/terminal.txt4
-rw-r--r--runtime/lua/tohtml.lua4
-rw-r--r--runtime/lua/vim/_defaults.lua8
-rw-r--r--runtime/lua/vim/termcap.lua2
-rw-r--r--runtime/lua/vim/ui/clipboard/osc52.lua2
-rw-r--r--src/nvim/api/ui.c6
-rw-r--r--src/nvim/terminal.c57
-rw-r--r--test/functional/editor/defaults_spec.lua2
-rw-r--r--test/functional/terminal/buffer_spec.lua38
-rw-r--r--test/functional/terminal/tui_spec.lua26
12 files changed, 129 insertions, 49 deletions
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
index 413802eea1..0e582ce0e7 100644
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -1004,22 +1004,32 @@ TermClose When a |terminal| job ends.
status
*TermRequest*
TermRequest When a |:terminal| child process emits an OSC,
- DCS or APC sequence. Sets |v:termrequest|. The
- |event-data| is the request string.
+ DCS, or APC sequence. Sets |v:termrequest|. The
+ |event-data| is a table with the following
+ fields:
+
+ - sequence: the received sequence
+ - cursor: (1,0)-indexed, buffer-relative
+ position of the cursor when the sequence was
+ received
+
*TermResponse*
TermResponse When Nvim receives an OSC or DCS response from
the host terminal. Sets |v:termresponse|. The
- |event-data| is the response string. May be
- triggered during another event (file I/O,
- a shell command, or anything else that takes
- time). Example: >lua
+ |event-data| is a table with the following fields:
+
+ - sequence: the received sequence
+
+ May be triggered during another event (file
+ I/O, a shell command, or anything else that
+ takes time). Example: >lua
-- Query the terminal palette for the RGB value of color 1
-- (red) using OSC 4
vim.api.nvim_create_autocmd('TermResponse', {
once = true,
callback = function(args)
- local resp = args.data
+ local resp = args.data.sequence
local r, g, b = resp:match("\027%]4;1;rgb:(%w+)/(%w+)/(%w+)")
end,
})
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 415a234254..4667af906f 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -111,6 +111,9 @@ EVENTS
• `history` argument indicating if the message was added to the history.
• new message kinds: "bufwrite", "completion", "list_cmd", "lua_print",
"search_cmd", "shell_out/err/ret", "undo", "verbose", wildlist".
+• |TermRequest| and |TermResponse| |event-data| is now a table. The "sequence"
+ field contains the received sequence. |TermRequest| also contains a "cursor"
+ field indicating the cursor's position when the sequence was received.
HIGHLIGHTS
@@ -396,6 +399,8 @@ TERMINAL
codes" mode is currently supported.
• The |terminal| emits a |TermRequest| autocommand event when the child process
emits an APC control sequence.
+• |TermRequest| has a "cursor" field in its |event-data| indicating the
+ cursor position when the sequence was received.
TREESITTER
diff --git a/runtime/doc/terminal.txt b/runtime/doc/terminal.txt
index 0ab7151728..4183bb8dcf 100644
--- a/runtime/doc/terminal.txt
+++ b/runtime/doc/terminal.txt
@@ -144,8 +144,8 @@ directory indicated in the request. >lua
vim.api.nvim_create_autocmd({ 'TermRequest' }, {
desc = 'Handles OSC 7 dir change requests',
callback = function(ev)
- if string.sub(vim.v.termrequest, 1, 4) == '\x1b]7;' then
- local dir = string.gsub(vim.v.termrequest, '\x1b]7;file://[^/]*', '')
+ if string.sub(ev.data.sequence, 1, 4) == '\x1b]7;' then
+ local dir = string.gsub(ev.data.sequence, '\x1b]7;file://[^/]*', '')
if vim.fn.isdirectory(dir) == 0 then
vim.notify('invalid dir: '..dir)
return
diff --git a/runtime/lua/tohtml.lua b/runtime/lua/tohtml.lua
index 4415a8cdca..6b8daab2c5 100644
--- a/runtime/lua/tohtml.lua
+++ b/runtime/lua/tohtml.lua
@@ -205,7 +205,9 @@ local function try_query_terminal_color(color)
once = true,
callback = function(args)
hex = '#'
- .. table.concat({ args.data:match('\027%]%d+;%d*;?rgb:(%w%w)%w%w/(%w%w)%w%w/(%w%w)%w%w') })
+ .. table.concat({
+ args.data.sequence:match('\027%]%d+;%d*;?rgb:(%w%w)%w%w/(%w%w)%w%w/(%w%w)%w%w'),
+ })
end,
})
if type(color) == 'number' then
diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua
index c2e4e76dd6..544b0acbcc 100644
--- a/runtime/lua/vim/_defaults.lua
+++ b/runtime/lua/vim/_defaults.lua
@@ -515,8 +515,8 @@ do
if channel == 0 then
return
end
- local fg_request = args.data == '\027]10;?'
- local bg_request = args.data == '\027]11;?'
+ local fg_request = args.data.sequence == '\027]10;?'
+ local bg_request = args.data.sequence == '\027]11;?'
if fg_request or bg_request then
-- WARN: This does not return the actual foreground/background color,
-- but rather returns:
@@ -712,7 +712,7 @@ do
nested = true,
desc = "Update the value of 'background' automatically based on the terminal emulator's background color",
callback = function(args)
- local resp = args.data ---@type string
+ local resp = args.data.sequence ---@type string
local r, g, b = parseosc11(resp)
if r and g and b then
local rr = parsecolor(r)
@@ -788,7 +788,7 @@ do
group = group,
nested = true,
callback = function(args)
- local resp = args.data ---@type string
+ local resp = args.data.sequence ---@type string
local decrqss = resp:match('^\027P1%$r([%d;:]+)m$')
if decrqss then
diff --git a/runtime/lua/vim/termcap.lua b/runtime/lua/vim/termcap.lua
index 4aa41bba9b..23666a337a 100644
--- a/runtime/lua/vim/termcap.lua
+++ b/runtime/lua/vim/termcap.lua
@@ -34,7 +34,7 @@ function M.query(caps, cb)
local id = vim.api.nvim_create_autocmd('TermResponse', {
nested = true,
callback = function(args)
- local resp = args.data ---@type string
+ local resp = args.data.sequence ---@type string
local k, rest = resp:match('^\027P1%+r(%x+)(.*)$')
if k and rest then
local cap = vim.text.hexdecode(k)
diff --git a/runtime/lua/vim/ui/clipboard/osc52.lua b/runtime/lua/vim/ui/clipboard/osc52.lua
index 50afbe63a5..73f64c9743 100644
--- a/runtime/lua/vim/ui/clipboard/osc52.lua
+++ b/runtime/lua/vim/ui/clipboard/osc52.lua
@@ -25,7 +25,7 @@ function M.paste(reg)
local contents = nil
local id = vim.api.nvim_create_autocmd('TermResponse', {
callback = function(args)
- local resp = args.data ---@type string
+ local resp = args.data.sequence ---@type string
local encoded = resp:match('\027%]52;%w?;([A-Za-z0-9+/=]*)')
if encoded then
contents = vim.base64.decode(encoded)
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index 41a09999d0..7aa4cf4576 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -507,7 +507,11 @@ void nvim_ui_term_event(uint64_t channel_id, String event, Object value, Error *
const String termresponse = value.data.string;
set_vim_var_string(VV_TERMRESPONSE, termresponse.data, (ptrdiff_t)termresponse.size);
- apply_autocmds_group(EVENT_TERMRESPONSE, NULL, NULL, false, AUGROUP_ALL, NULL, NULL, &value);
+
+ MAXSIZE_TEMP_DICT(data, 1);
+ PUT_C(data, "sequence", value);
+ apply_autocmds_group(EVENT_TERMRESPONSE, NULL, NULL, false, AUGROUP_ALL, NULL, NULL,
+ &DICT_OBJ(data));
}
}
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index 47630ddea9..e0ebcc05b8 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -186,7 +186,7 @@ struct terminal {
char *selection_buffer; ///< libvterm selection buffer
StringBuilder selection; ///< Growable array containing full selection data
- StringBuilder termrequest_buffer; ///< Growable array containing unfinished request payload
+ StringBuilder termrequest_buffer; ///< Growable array containing unfinished request sequence
size_t refcount; // reference count
};
@@ -213,16 +213,36 @@ static Set(ptr_t) invalidated_terminals = SET_INIT;
static void emit_termrequest(void **argv)
{
Terminal *term = argv[0];
- char *payload = argv[1];
- size_t payload_length = (size_t)argv[2];
+ char *sequence = argv[1];
+ size_t sequence_length = (size_t)argv[2];
StringBuilder *pending_send = argv[3];
+ int row = (int)(intptr_t)argv[4];
+ int col = (int)(intptr_t)argv[5];
+
+ if (term->sb_pending > 0) {
+ // Don't emit the event while there is pending scrollback because we need
+ // the buffer contents to be fully updated. If this is the case, re-schedule
+ // the event.
+ multiqueue_put(main_loop.events, emit_termrequest, term, sequence, (void *)sequence_length,
+ pending_send, (void *)(intptr_t)row, (void *)(intptr_t)col);
+ 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(col));
+
+ MAXSIZE_TEMP_DICT(data, 2);
+ String termrequest = { .data = sequence, .size = sequence_length };
+ PUT_C(data, "sequence", STRING_OBJ(termrequest));
+ PUT_C(data, "cursor", ARRAY_OBJ(cursor));
buf_T *buf = handle_get_buffer(term->buf_handle);
- String termrequest = { .data = payload, .size = payload_length };
- Object data = STRING_OBJ(termrequest);
- set_vim_var_string(VV_TERMREQUEST, payload, (ptrdiff_t)payload_length);
- apply_autocmds_group(EVENT_TERMREQUEST, NULL, NULL, false, AUGROUP_ALL, buf, NULL, &data);
- xfree(payload);
+ apply_autocmds_group(EVENT_TERMREQUEST, NULL, NULL, false, AUGROUP_ALL, buf, NULL,
+ &DICT_OBJ(data));
+ xfree(sequence);
StringBuilder *term_pending_send = term->pending.send;
term->pending.send = NULL;
@@ -236,12 +256,15 @@ static void emit_termrequest(void **argv)
xfree(pending_send);
}
-static void schedule_termrequest(Terminal *term, char *payload, size_t payload_length)
+static void schedule_termrequest(Terminal *term, char *sequence, size_t sequence_length)
{
term->pending.send = xmalloc(sizeof(StringBuilder));
kv_init(*term->pending.send);
- multiqueue_put(main_loop.events, emit_termrequest, term, payload, (void *)payload_length,
- term->pending.send);
+
+ int line = row_to_linenr(term, term->cursor.row);
+ multiqueue_put(main_loop.events, emit_termrequest, term, sequence, (void *)sequence_length,
+ term->pending.send, (void *)(intptr_t)line,
+ (void *)(intptr_t)term->cursor.col);
}
static int parse_osc8(VTermStringFragment frag, int *attr)
@@ -315,8 +338,8 @@ static int on_osc(int command, VTermStringFragment frag, void *user)
}
kv_concat_len(term->termrequest_buffer, frag.str, frag.len);
if (frag.final) {
- char *payload = xmemdup(term->termrequest_buffer.items, term->termrequest_buffer.size);
- schedule_termrequest(user, payload, term->termrequest_buffer.size);
+ char *sequence = xmemdup(term->termrequest_buffer.items, term->termrequest_buffer.size);
+ schedule_termrequest(user, sequence, term->termrequest_buffer.size);
}
return 1;
}
@@ -338,8 +361,8 @@ static int on_dcs(const char *command, size_t commandlen, VTermStringFragment fr
}
kv_concat_len(term->termrequest_buffer, frag.str, frag.len);
if (frag.final) {
- char *payload = xmemdup(term->termrequest_buffer.items, term->termrequest_buffer.size);
- schedule_termrequest(user, payload, term->termrequest_buffer.size);
+ char *sequence = xmemdup(term->termrequest_buffer.items, term->termrequest_buffer.size);
+ schedule_termrequest(user, sequence, term->termrequest_buffer.size);
}
return 1;
}
@@ -361,8 +384,8 @@ static int on_apc(VTermStringFragment frag, void *user)
}
kv_concat_len(term->termrequest_buffer, frag.str, frag.len);
if (frag.final) {
- char *payload = xmemdup(term->termrequest_buffer.items, term->termrequest_buffer.size);
- schedule_termrequest(user, payload, term->termrequest_buffer.size);
+ char *sequence = xmemdup(term->termrequest_buffer.items, term->termrequest_buffer.size);
+ schedule_termrequest(user, sequence, term->termrequest_buffer.size);
}
return 1;
}
diff --git a/test/functional/editor/defaults_spec.lua b/test/functional/editor/defaults_spec.lua
index 876810ce6f..9843238e35 100644
--- a/test/functional/editor/defaults_spec.lua
+++ b/test/functional/editor/defaults_spec.lua
@@ -10,7 +10,7 @@ local Screen = require('test.functional.ui.screen')
describe('default', function()
describe('autocommands', function()
- it('nvim_terminal.TermClose closes terminal with default shell on success', function()
+ it('nvim.terminal.TermClose closes terminal with default shell on success', function()
n.clear()
n.api.nvim_set_option_value('shell', n.testprg('shell-test'), {})
n.command('set shellcmdflag=EXIT shellredir= shellpipe= shellquote= shellxquote=')
diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua
index f2d679bd5d..b134aa0225 100644
--- a/test/functional/terminal/buffer_spec.lua
+++ b/test/functional/terminal/buffer_spec.lua
@@ -373,7 +373,7 @@ describe(':terminal buffer', function()
})
vim.api.nvim_create_autocmd('TermRequest', {
callback = function(args)
- if args.data == '\027]11;?' then
+ if args.data.sequence == '\027]11;?' then
table.insert(_G.input, '\027]11;rgb:0000/0000/0000\027\\')
end
end
@@ -389,6 +389,42 @@ describe(':terminal buffer', function()
}, exec_lua('return _G.input'))
end)
+ it('TermRequest includes cursor position #31609', function()
+ command('autocmd! nvim.terminal TermRequest')
+ local screen = Screen.new(50, 10)
+ local term = exec_lua([[
+ _G.cursor = {}
+ local term = vim.api.nvim_open_term(0, {})
+ vim.api.nvim_create_autocmd('TermRequest', {
+ callback = function(args)
+ _G.cursor = args.data.cursor
+ end
+ })
+ return term
+ ]])
+ -- Enter terminal mode so that the cursor follows the output
+ feed('a')
+
+ -- Put some lines into the scrollback. This tests the conversion from terminal line to buffer
+ -- line.
+ api.nvim_chan_send(term, string.rep('>\n', 20))
+ screen:expect([[
+ > |*8
+ ^ |
+ {5:-- TERMINAL --} |
+ ]])
+
+ -- Emit an OSC escape sequence
+ api.nvim_chan_send(term, 'Hello\nworld!\027]133;D\027\\')
+ screen:expect([[
+ > |*7
+ Hello |
+ world!^ |
+ {5:-- TERMINAL --} |
+ ]])
+ eq({ 22, 6 }, exec_lua('return _G.cursor'))
+ end)
+
it('no heap-buffer-overflow when using jobstart("echo",{term=true}) #3161', function()
local testfilename = 'Xtestfile-functional-terminal-buffers_spec'
write_file(testfilename, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaa')
diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua
index e2adcb66df..a2d5b39f84 100644
--- a/test/functional/terminal/tui_spec.lua
+++ b/test/functional/terminal/tui_spec.lua
@@ -215,7 +215,7 @@ describe('TUI', function()
_G.termresponse = nil
vim.api.nvim_create_autocmd('TermResponse', {
once = true,
- callback = function(ev) _G.termresponse = ev.data end,
+ callback = function(ev) _G.termresponse = ev.data.sequence end,
})
]])
feed_data('\027P0$r\027\\')
@@ -2199,7 +2199,7 @@ describe('TUI', function()
vim.api.nvim_create_autocmd('TermRequest', {
buffer = buf,
callback = function(args)
- local req = args.data
+ local req = args.data.sequence
if not req then
return
end
@@ -3171,12 +3171,12 @@ describe('TUI', function()
exec_lua([[
vim.api.nvim_create_autocmd('TermRequest', {
callback = function(args)
- local req = args.data
- local payload = req:match('^\027P%+q([%x;]+)$')
- if payload then
+ local req = args.data.sequence
+ local sequence = req:match('^\027P%+q([%x;]+)$')
+ if sequence then
local t = {}
- for cap in vim.gsplit(payload, ';') do
- local resp = string.format('\027P1+r%s\027\\', payload)
+ for cap in vim.gsplit(sequence, ';') do
+ local resp = string.format('\027P1+r%s\027\\', sequence)
vim.api.nvim_chan_send(vim.bo[args.buf].channel, resp)
t[vim.text.hexdecode(cap)] = true
end
@@ -3222,7 +3222,7 @@ describe('TUI', function()
exec_lua([[
vim.api.nvim_create_autocmd('TermRequest', {
callback = function(args)
- local req = args.data
+ local req = args.data.sequence
vim.g.termrequest = req
local xtgettcap = req:match('^\027P%+q([%x;]+)$')
if xtgettcap then
@@ -3274,10 +3274,10 @@ describe('TUI', function()
exec_lua([[
vim.api.nvim_create_autocmd('TermRequest', {
callback = function(args)
- local req = args.data
- local payload = req:match('^\027P%+q([%x;]+)$')
- if payload and vim.text.hexdecode(payload) == 'Ms' then
- local resp = string.format('\027P1+r%s=%s\027\\', payload, vim.text.hexencode('\027]52;;\027\\'))
+ local req = args.data.sequence
+ local sequence = req:match('^\027P%+q([%x;]+)$')
+ if sequence and vim.text.hexdecode(sequence) == 'Ms' then
+ local resp = string.format('\027P1+r%s=%s\027\\', sequence, vim.text.hexencode('\027]52;;\027\\'))
vim.api.nvim_chan_send(vim.bo[args.buf].channel, resp)
return true
end
@@ -3353,7 +3353,7 @@ describe('TUI bg color', function()
exec_lua([[
vim.api.nvim_create_autocmd('TermRequest', {
callback = function(args)
- local req = args.data
+ local req = args.data.sequence
if req == '\027]11;?' then
vim.g.oscrequest = true
return true