mirror of
https://github.com/neovim/neovim.git
synced 2026-08-27 17:41:48 +00: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:
|
fields:
|
||||||
|
|
||||||
- sequence: the received sequence
|
- sequence: the received sequence
|
||||||
|
- terminator: the received sequence terminator (i.e. BEL or ST)
|
||||||
- cursor: (1,0)-indexed, buffer-relative
|
- cursor: (1,0)-indexed, buffer-relative
|
||||||
position of the cursor when the sequence was
|
position of the cursor when the sequence was
|
||||||
received (line number may be <= 0 if the
|
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.
|
• 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
|
• |MarkSet| is triggered after a |mark| is set by the user (currently doesn't
|
||||||
support implicit marks like |'[| or |'<|, …).
|
support implicit marks like |'[| or |'<|, …).
|
||||||
|
• New `terminator` parameter for |TermRequest| event.
|
||||||
|
|
||||||
HIGHLIGHTS
|
HIGHLIGHTS
|
||||||
|
|
||||||
|
|||||||
@@ -567,7 +567,14 @@ do
|
|||||||
red, green, blue = 65535, 65535, 65535
|
red, green, blue = 65535, 65535, 65535
|
||||||
end
|
end
|
||||||
local command = fg_request and 10 or 11
|
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)
|
vim.api.nvim_chan_send(channel, data)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|||||||
@@ -202,6 +202,7 @@ struct terminal {
|
|||||||
StringBuilder selection; ///< Growable array containing full selection data
|
StringBuilder selection; ///< Growable array containing full selection data
|
||||||
|
|
||||||
StringBuilder termrequest_buffer; ///< Growable array containing unfinished request sequence
|
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
|
size_t refcount; // reference count
|
||||||
};
|
};
|
||||||
@@ -234,6 +235,7 @@ static void emit_termrequest(void **argv)
|
|||||||
int row = (int)(intptr_t)argv[4];
|
int row = (int)(intptr_t)argv[4];
|
||||||
int col = (int)(intptr_t)argv[5];
|
int col = (int)(intptr_t)argv[5];
|
||||||
size_t sb_deleted = (size_t)(intptr_t)argv[6];
|
size_t sb_deleted = (size_t)(intptr_t)argv[6];
|
||||||
|
VTermTerminator terminator = (VTermTerminator)(intptr_t)argv[7];
|
||||||
|
|
||||||
if (term->sb_pending > 0) {
|
if (term->sb_pending > 0) {
|
||||||
// Don't emit the event while there is pending scrollback because we need
|
// 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.
|
// terminal is refreshed and the pending scrollback is cleared.
|
||||||
multiqueue_put(term->pending.events, emit_termrequest, term, sequence, (void *)sequence_length,
|
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);
|
(void *)(intptr_t)sb_deleted, (void *)(intptr_t)terminator);
|
||||||
return;
|
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(row - (int64_t)(term->sb_deleted - sb_deleted)));
|
||||||
ADD_C(cursor, INTEGER_OBJ(col));
|
ADD_C(cursor, INTEGER_OBJ(col));
|
||||||
|
|
||||||
MAXSIZE_TEMP_DICT(data, 2);
|
MAXSIZE_TEMP_DICT(data, 3);
|
||||||
String termrequest = { .data = sequence, .size = sequence_length };
|
String termrequest = { .data = sequence, .size = sequence_length };
|
||||||
PUT_C(data, "sequence", STRING_OBJ(termrequest));
|
PUT_C(data, "sequence", STRING_OBJ(termrequest));
|
||||||
PUT_C(data, "cursor", ARRAY_OBJ(cursor));
|
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);
|
buf_T *buf = handle_get_buffer(term->buf_handle);
|
||||||
apply_autocmds_group(EVENT_TERMREQUEST, NULL, NULL, true, AUGROUP_ALL, buf, NULL,
|
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),
|
xmemdup(term->termrequest_buffer.items, term->termrequest_buffer.size),
|
||||||
(void *)(intptr_t)term->termrequest_buffer.size, term->pending.send,
|
(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);
|
(void *)(intptr_t)term->sb_deleted,
|
||||||
|
(void *)(intptr_t)term->termrequest_terminator);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int parse_osc8(const char *str, int *attr)
|
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);
|
kv_concat_len(term->termrequest_buffer, frag.str, frag.len);
|
||||||
if (frag.final) {
|
if (frag.final) {
|
||||||
|
term->termrequest_terminator = frag.terminator;
|
||||||
if (has_event(EVENT_TERMREQUEST)) {
|
if (has_event(EVENT_TERMREQUEST)) {
|
||||||
schedule_termrequest(term);
|
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);
|
kv_concat_len(term->termrequest_buffer, frag.str, frag.len);
|
||||||
if (frag.final) {
|
if (frag.final) {
|
||||||
|
term->termrequest_terminator = frag.terminator;
|
||||||
schedule_termrequest(term);
|
schedule_termrequest(term);
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
@@ -392,6 +400,7 @@ static int on_apc(VTermStringFragment frag, void *user)
|
|||||||
}
|
}
|
||||||
kv_concat_len(term->termrequest_buffer, frag.str, frag.len);
|
kv_concat_len(term->termrequest_buffer, frag.str, frag.len);
|
||||||
if (frag.final) {
|
if (frag.final) {
|
||||||
|
term->termrequest_terminator = frag.terminator;
|
||||||
schedule_termrequest(term);
|
schedule_termrequest(term);
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
|
|||||||
@@ -72,13 +72,15 @@ static void do_escape(VTerm *vt, char command)
|
|||||||
DEBUG_LOG("libvterm: Unhandled escape ESC 0x%02x\n", 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 = {
|
VTermStringFragment frag = {
|
||||||
.str = str,
|
.str = str,
|
||||||
.len = len,
|
.len = len,
|
||||||
.initial = vt->parser.string_initial,
|
.initial = vt->parser.string_initial,
|
||||||
.final = final,
|
.final = final,
|
||||||
|
.terminator = terminator,
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (vt->parser.state) {
|
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 (c == 0x00 || c == 0x7f) { // NUL, DEL
|
||||||
if (IS_STRING_STATE()) {
|
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;
|
string_start = bytes + pos + 1;
|
||||||
}
|
}
|
||||||
if (vt->parser.emit_nul) {
|
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
|
continue; // All other C0s permitted in SOS
|
||||||
}
|
}
|
||||||
if (IS_STRING_STATE()) {
|
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);
|
do_control(vt, c);
|
||||||
if (IS_STRING_STATE()) {
|
if (IS_STRING_STATE()) {
|
||||||
@@ -316,7 +320,8 @@ string_state:
|
|||||||
case PM:
|
case PM:
|
||||||
case SOS:
|
case SOS:
|
||||||
if (c == 0x07 || (c1_allowed && c == 0x9c)) {
|
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();
|
ENTER_NORMAL_STATE();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -395,7 +400,7 @@ string_state:
|
|||||||
if (vt->parser.in_esc) {
|
if (vt->parser.in_esc) {
|
||||||
string_len -= 1;
|
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,
|
VTERM_N_PROPS,
|
||||||
} VTermProp;
|
} VTermProp;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
VTERM_TERMINATOR_BEL, // \x07
|
||||||
|
VTERM_TERMINATOR_ST, // \x1b\x5c
|
||||||
|
} VTermTerminator;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char *str;
|
const char *str;
|
||||||
size_t len : 30;
|
size_t len : 30;
|
||||||
bool initial : 1;
|
bool initial : 1;
|
||||||
bool final : 1;
|
bool final : 1;
|
||||||
|
VTermTerminator terminator;
|
||||||
} VTermStringFragment;
|
} VTermStringFragment;
|
||||||
|
|
||||||
typedef union {
|
typedef union {
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
local n = require('test.functional.testnvim')()
|
local n = require('test.functional.testnvim')()
|
||||||
|
|
||||||
local clear = n.clear
|
|
||||||
local api = n.api
|
local api = n.api
|
||||||
local assert_alive = n.assert_alive
|
local assert_alive = n.assert_alive
|
||||||
|
local clear = n.clear
|
||||||
|
local exec_lua = n.exec_lua
|
||||||
|
|
||||||
local OSC_PREFIX = string.char(0x1b, 0x5d)
|
local OSC_PREFIX = string.char(0x1b, 0x5d)
|
||||||
local BEL = string.char(0x07)
|
local BEL = string.char(0x07)
|
||||||
|
local ST = string.char(0x1b, 0x5c)
|
||||||
local NUL = string.char(0x00)
|
local NUL = string.char(0x00)
|
||||||
|
|
||||||
describe(':terminal', function()
|
describe(':terminal', function()
|
||||||
@@ -60,4 +62,34 @@ describe(':terminal', function()
|
|||||||
api.nvim_chan_send(chan, input)
|
api.nvim_chan_send(chan, input)
|
||||||
assert_alive()
|
assert_alive()
|
||||||
end)
|
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)
|
end)
|
||||||
|
|||||||
Reference in New Issue
Block a user