diff options
-rw-r--r-- | runtime/doc/autocmd.txt | 24 | ||||
-rw-r--r-- | runtime/doc/news.txt | 5 | ||||
-rw-r--r-- | runtime/doc/terminal.txt | 4 | ||||
-rw-r--r-- | runtime/lua/tohtml.lua | 4 | ||||
-rw-r--r-- | runtime/lua/vim/_defaults.lua | 8 | ||||
-rw-r--r-- | runtime/lua/vim/termcap.lua | 2 | ||||
-rw-r--r-- | runtime/lua/vim/ui/clipboard/osc52.lua | 2 | ||||
-rw-r--r-- | src/nvim/api/ui.c | 6 | ||||
-rw-r--r-- | src/nvim/terminal.c | 57 | ||||
-rw-r--r-- | test/functional/editor/defaults_spec.lua | 2 | ||||
-rw-r--r-- | test/functional/terminal/buffer_spec.lua | 38 | ||||
-rw-r--r-- | test/functional/terminal/tui_spec.lua | 26 |
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 |