feat(term): trigger TermRequest for APC (#32407)

Co-authored-by: Gregory Anders <greg@gpanders.com>
Co-authored-by: zeertzjq <zeertzjq@outlook.com>
This commit is contained in:
Till Bungert 2025-02-13 15:24:01 +01:00 committed by GitHub
parent e4c6e732fd
commit 93480f7fba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 67 additions and 16 deletions

View File

@ -1004,8 +1004,8 @@ TermClose When a |terminal| job ends.
Sets these |v:event| keys:
status
*TermRequest*
TermRequest When a |:terminal| child process emits an OSC
or DCS sequence. Sets |v:termrequest|. The
TermRequest When a |:terminal| child process emits an OSC,
DCS or APC sequence. Sets |v:termrequest|. The
|event-data| is the request string.
*TermResponse*
TermResponse When Nvim receives an OSC or DCS response from

View File

@ -372,6 +372,8 @@ TERMINAL
• The |terminal| has experimental support for the Kitty keyboard protocol
(sometimes called "CSI u" key encoding). Only the "Disambiguate escape
codes" mode is currently supported.
• The |terminal| emits a |TermRequest| autocommand event when the child process
emits an APC control sequence.
TREESITTER

View File

@ -663,7 +663,7 @@ v:t_string Value of |String| type. Read-only. See: |type()|
*v:termrequest* *termrequest-variable*
v:termrequest
The value of the most recent OSC or DCS control sequence
The value of the most recent OSC, DCS or APC control sequence
sent from a process running in the embedded |terminal|.
This can be read in a |TermRequest| event handler to respond
to queries from embedded applications.

View File

@ -700,7 +700,7 @@ vim.v.t_number = ...
--- @type integer
vim.v.t_string = ...
--- The value of the most recent OSC or DCS control sequence
--- The value of the most recent OSC, DCS or APC control sequence
--- sent from a process running in the embedded `terminal`.
--- This can be read in a `TermRequest` event handler to respond
--- to queries from embedded applications.

View File

@ -183,8 +183,10 @@ struct terminal {
bool color_set[16];
char *selection_buffer; /// libvterm selection buffer
StringBuilder selection; /// Growable array containing full selection data
char *selection_buffer; ///< libvterm selection buffer
StringBuilder selection; ///< Growable array containing full selection data
StringBuilder termrequest_buffer; ///< Growable array containing unfinished request payload
size_t refcount; // reference count
};
@ -307,15 +309,22 @@ static int on_osc(int command, VTermStringFragment frag, void *user)
return 1;
}
StringBuilder request = KV_INITIAL_VALUE;
kv_printf(request, "\x1b]%d;", command);
kv_concat_len(request, frag.str, frag.len);
schedule_termrequest(term, request.items, request.size);
if (frag.initial) {
kv_size(term->termrequest_buffer) = 0;
kv_printf(term->termrequest_buffer, "\x1b]%d;", command);
}
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);
}
return 1;
}
static int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user)
{
Terminal *term = user;
if (command == NULL || frag.str == NULL) {
return 0;
}
@ -323,10 +332,38 @@ static int on_dcs(const char *command, size_t commandlen, VTermStringFragment fr
return 1;
}
StringBuilder request = KV_INITIAL_VALUE;
kv_printf(request, "\x1bP%*s", (int)commandlen, command);
kv_concat_len(request, frag.str, frag.len);
schedule_termrequest(user, request.items, request.size);
if (frag.initial) {
kv_size(term->termrequest_buffer) = 0;
kv_printf(term->termrequest_buffer, "\x1bP%*s", (int)commandlen, command);
}
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);
}
return 1;
}
static int on_apc(VTermStringFragment frag, void *user)
{
Terminal *term = user;
if (frag.str == NULL || frag.len == 0) {
return 0;
}
if (!has_event(EVENT_TERMREQUEST)) {
return 1;
}
if (frag.initial) {
kv_size(term->termrequest_buffer) = 0;
kv_printf(term->termrequest_buffer, "\x1b_");
}
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);
}
return 1;
}
@ -335,7 +372,7 @@ static VTermStateFallbacks vterm_fallbacks = {
.csi = NULL,
.osc = on_osc,
.dcs = on_dcs,
.apc = NULL,
.apc = on_apc,
.pm = NULL,
.sos = NULL,
};
@ -924,6 +961,7 @@ void terminal_destroy(Terminal **termpp)
xfree(term->title);
xfree(term->selection_buffer);
kv_destroy(term->selection);
kv_destroy(term->termrequest_buffer);
vterm_free(term->vt);
xfree(term);
*termpp = NULL; // coverity[dead-store]

View File

@ -799,7 +799,7 @@ M.vars = {
termrequest = {
type = 'string',
desc = [=[
The value of the most recent OSC or DCS control sequence
The value of the most recent OSC, DCS or APC control sequence
sent from a process running in the embedded |terminal|.
This can be read in a |TermRequest| event handler to respond
to queries from embedded applications.

View File

@ -350,6 +350,17 @@ describe(':terminal buffer', function()
eq(termbuf, eval('g:termbuf'))
end)
it('emits TermRequest events for APC', function()
local term = api.nvim_open_term(0, {})
-- cwd will be inserted in a file URI, which cannot contain backs
local cwd = t.fix_slashes(fn.getcwd())
local parent = cwd:match('^(.+/)')
local expected = '\027_Gfile://host' .. parent
api.nvim_chan_send(term, string.format('%s\027\\', expected))
eq(expected, eval('v:termrequest'))
end)
it('TermRequest synchronization #27572', function()
command('autocmd! nvim.terminal TermRequest')
local term = exec_lua([[