mirror of
https://github.com/neovim/neovim.git
synced 2026-01-03 01:46:31 +10:00
feat(terminal): include sequence terminator in TermRequest event (#37152)
Problem: Terminals should respond with the terminator (either BEL or ST) used in the query so that clients can reliably parse the responses. The `TermRequest` autocmd used to handle background color requests in the terminal does not have access to the original sequence terminator, so it always uses BEL. #37018 Solution: Update vterm parsing to include the terminator type, then forward this data into the emitted `TermRequest` events for OSC/DCS/APC sequences. Update the foreground/background `TermRequest` callback to use the same terminator as the original request. Details: I didn't add the terminator to the `TermResponse` event. However, I assume the `TermResponse` event doesn't care about the terminator because the sequence is already parsed. I also didn't update any of the functions in `src/nvim/vterm/state.c` that write out responses. It looked like those all pretty much used ST, and it would be a much larger set of changes. In that same file, there's also logic for 8 bit ST sequences, but from what I can tell, 8 bit doesn't really work (see `:h xterm-8bit`), so I didn't use the 8 bit ST at all.
This commit is contained in:
@@ -1081,6 +1081,7 @@ TermRequest When a |:terminal| child process emits an OSC,
|
||||
fields:
|
||||
|
||||
- sequence: the received sequence
|
||||
- terminator: the received sequence terminator (i.e. BEL or ST)
|
||||
- cursor: (1,0)-indexed, buffer-relative
|
||||
position of the cursor when the sequence was
|
||||
received (line number may be <= 0 if the
|
||||
|
||||
@@ -231,6 +231,7 @@ EVENTS
|
||||
• Creating or updating a progress message with |nvim_echo()| triggers a |Progress| event.
|
||||
• |MarkSet| is triggered after a |mark| is set by the user (currently doesn't
|
||||
support implicit marks like |'[| or |'<|, …).
|
||||
• New `terminator` parameter for |TermRequest| event.
|
||||
|
||||
HIGHLIGHTS
|
||||
|
||||
|
||||
@@ -567,7 +567,14 @@ do
|
||||
red, green, blue = 65535, 65535, 65535
|
||||
end
|
||||
local command = fg_request and 10 or 11
|
||||
local data = string.format('\027]%d;rgb:%04x/%04x/%04x\007', command, red, green, blue)
|
||||
local data = string.format(
|
||||
'\027]%d;rgb:%04x/%04x/%04x%s',
|
||||
command,
|
||||
red,
|
||||
green,
|
||||
blue,
|
||||
args.data.terminator
|
||||
)
|
||||
vim.api.nvim_chan_send(channel, data)
|
||||
end
|
||||
end,
|
||||
|
||||
@@ -202,6 +202,7 @@ struct terminal {
|
||||
StringBuilder selection; ///< Growable array containing full selection data
|
||||
|
||||
StringBuilder termrequest_buffer; ///< Growable array containing unfinished request sequence
|
||||
VTermTerminator termrequest_terminator; ///< Terminator (BEL or ST) used in the termrequest
|
||||
|
||||
size_t refcount; // reference count
|
||||
};
|
||||
@@ -234,6 +235,7 @@ static void emit_termrequest(void **argv)
|
||||
int row = (int)(intptr_t)argv[4];
|
||||
int col = (int)(intptr_t)argv[5];
|
||||
size_t sb_deleted = (size_t)(intptr_t)argv[6];
|
||||
VTermTerminator terminator = (VTermTerminator)(intptr_t)argv[7];
|
||||
|
||||
if (term->sb_pending > 0) {
|
||||
// Don't emit the event while there is pending scrollback because we need
|
||||
@@ -242,7 +244,7 @@ static void emit_termrequest(void **argv)
|
||||
// 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,
|
||||
(void *)(intptr_t)sb_deleted);
|
||||
(void *)(intptr_t)sb_deleted, (void *)(intptr_t)terminator);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -252,10 +254,13 @@ static void emit_termrequest(void **argv)
|
||||
ADD_C(cursor, INTEGER_OBJ(row - (int64_t)(term->sb_deleted - sb_deleted)));
|
||||
ADD_C(cursor, INTEGER_OBJ(col));
|
||||
|
||||
MAXSIZE_TEMP_DICT(data, 2);
|
||||
MAXSIZE_TEMP_DICT(data, 3);
|
||||
String termrequest = { .data = sequence, .size = sequence_length };
|
||||
PUT_C(data, "sequence", STRING_OBJ(termrequest));
|
||||
PUT_C(data, "cursor", ARRAY_OBJ(cursor));
|
||||
PUT_C(data, "terminator",
|
||||
terminator ==
|
||||
VTERM_TERMINATOR_BEL ? STATIC_CSTR_AS_OBJ("\x07") : STATIC_CSTR_AS_OBJ("\x1b\\"));
|
||||
|
||||
buf_T *buf = handle_get_buffer(term->buf_handle);
|
||||
apply_autocmds_group(EVENT_TERMREQUEST, NULL, NULL, true, AUGROUP_ALL, buf, NULL,
|
||||
@@ -284,7 +289,8 @@ static void schedule_termrequest(Terminal *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)term->sb_deleted);
|
||||
(void *)(intptr_t)term->sb_deleted,
|
||||
(void *)(intptr_t)term->termrequest_terminator);
|
||||
}
|
||||
|
||||
static int parse_osc8(const char *str, int *attr)
|
||||
@@ -336,6 +342,7 @@ static int on_osc(int command, VTermStringFragment frag, void *user)
|
||||
}
|
||||
kv_concat_len(term->termrequest_buffer, frag.str, frag.len);
|
||||
if (frag.final) {
|
||||
term->termrequest_terminator = frag.terminator;
|
||||
if (has_event(EVENT_TERMREQUEST)) {
|
||||
schedule_termrequest(term);
|
||||
}
|
||||
@@ -370,6 +377,7 @@ 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) {
|
||||
term->termrequest_terminator = frag.terminator;
|
||||
schedule_termrequest(term);
|
||||
}
|
||||
return 1;
|
||||
@@ -392,6 +400,7 @@ static int on_apc(VTermStringFragment frag, void *user)
|
||||
}
|
||||
kv_concat_len(term->termrequest_buffer, frag.str, frag.len);
|
||||
if (frag.final) {
|
||||
term->termrequest_terminator = frag.terminator;
|
||||
schedule_termrequest(term);
|
||||
}
|
||||
return 1;
|
||||
|
||||
@@ -72,13 +72,15 @@ static void do_escape(VTerm *vt, char command)
|
||||
DEBUG_LOG("libvterm: Unhandled escape ESC 0x%02x\n", command);
|
||||
}
|
||||
|
||||
static void string_fragment(VTerm *vt, const char *str, size_t len, bool final)
|
||||
static void string_fragment(VTerm *vt, const char *str, size_t len, bool final,
|
||||
VTermTerminator terminator)
|
||||
{
|
||||
VTermStringFragment frag = {
|
||||
.str = str,
|
||||
.len = len,
|
||||
.initial = vt->parser.string_initial,
|
||||
.final = final,
|
||||
.terminator = terminator,
|
||||
};
|
||||
|
||||
switch (vt->parser.state) {
|
||||
@@ -160,7 +162,8 @@ size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len)
|
||||
|
||||
if (c == 0x00 || c == 0x7f) { // NUL, DEL
|
||||
if (IS_STRING_STATE()) {
|
||||
string_fragment(vt, string_start, (size_t)(bytes + pos - string_start), false);
|
||||
string_fragment(vt, string_start, (size_t)(bytes + pos - string_start), false,
|
||||
VTERM_TERMINATOR_ST);
|
||||
string_start = bytes + pos + 1;
|
||||
}
|
||||
if (vt->parser.emit_nul) {
|
||||
@@ -188,7 +191,8 @@ size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len)
|
||||
continue; // All other C0s permitted in SOS
|
||||
}
|
||||
if (IS_STRING_STATE()) {
|
||||
string_fragment(vt, string_start, (size_t)(bytes + pos - string_start), false);
|
||||
string_fragment(vt, string_start, (size_t)(bytes + pos - string_start), false,
|
||||
VTERM_TERMINATOR_ST);
|
||||
}
|
||||
do_control(vt, c);
|
||||
if (IS_STRING_STATE()) {
|
||||
@@ -316,7 +320,8 @@ string_state:
|
||||
case PM:
|
||||
case SOS:
|
||||
if (c == 0x07 || (c1_allowed && c == 0x9c)) {
|
||||
string_fragment(vt, string_start, string_len, true);
|
||||
string_fragment(vt, string_start, string_len, true,
|
||||
c == 0x07 ? VTERM_TERMINATOR_BEL : VTERM_TERMINATOR_ST);
|
||||
ENTER_NORMAL_STATE();
|
||||
}
|
||||
break;
|
||||
@@ -395,7 +400,7 @@ string_state:
|
||||
if (vt->parser.in_esc) {
|
||||
string_len -= 1;
|
||||
}
|
||||
string_fragment(vt, string_start, string_len, false);
|
||||
string_fragment(vt, string_start, string_len, false, VTERM_TERMINATOR_ST);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,11 +91,17 @@ typedef enum {
|
||||
VTERM_N_PROPS,
|
||||
} VTermProp;
|
||||
|
||||
typedef enum {
|
||||
VTERM_TERMINATOR_BEL, // \x07
|
||||
VTERM_TERMINATOR_ST, // \x1b\x5c
|
||||
} VTermTerminator;
|
||||
|
||||
typedef struct {
|
||||
const char *str;
|
||||
size_t len : 30;
|
||||
bool initial : 1;
|
||||
bool final : 1;
|
||||
VTermTerminator terminator;
|
||||
} VTermStringFragment;
|
||||
|
||||
typedef union {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
local n = require('test.functional.testnvim')()
|
||||
|
||||
local clear = n.clear
|
||||
local api = n.api
|
||||
local assert_alive = n.assert_alive
|
||||
local clear = n.clear
|
||||
local exec_lua = n.exec_lua
|
||||
|
||||
local OSC_PREFIX = string.char(0x1b, 0x5d)
|
||||
local BEL = string.char(0x07)
|
||||
local ST = string.char(0x1b, 0x5c)
|
||||
local NUL = string.char(0x00)
|
||||
|
||||
describe(':terminal', function()
|
||||
@@ -60,4 +62,34 @@ describe(':terminal', function()
|
||||
api.nvim_chan_send(chan, input)
|
||||
assert_alive()
|
||||
end)
|
||||
|
||||
it('uses terminator matching query for OSC TermRequest #37018', function()
|
||||
local chan = api.nvim_open_term(0, {})
|
||||
exec_lua([[
|
||||
vim.api.nvim_create_autocmd("TermRequest", {
|
||||
callback = function(args)
|
||||
_G.osc10_response = {sequence = args.data.sequence, terminator = args.data.terminator }
|
||||
end
|
||||
})
|
||||
]])
|
||||
|
||||
local function send_osc_with_terminator(terminator)
|
||||
local input = OSC_PREFIX .. '10;?' .. terminator
|
||||
api.nvim_chan_send(chan, input)
|
||||
end
|
||||
|
||||
send_osc_with_terminator(BEL)
|
||||
--- @type string
|
||||
assert.same(
|
||||
{ sequence = OSC_PREFIX .. '10;?', terminator = BEL },
|
||||
exec_lua([[return _G.osc10_response]])
|
||||
)
|
||||
|
||||
send_osc_with_terminator(ST)
|
||||
--- @type string
|
||||
assert.same(
|
||||
{ sequence = OSC_PREFIX .. '10;?', terminator = ST },
|
||||
exec_lua([[return _G.osc10_response]])
|
||||
)
|
||||
end)
|
||||
end)
|
||||
|
||||
Reference in New Issue
Block a user