mirror of
https://github.com/neovim/neovim.git
synced 2024-12-19 10:45:16 -07:00
feat(terminal): trigger TermRequest autocommand events (#22159)
This commit implements a new TermRequest autocommand event and has Neovim emit this event when children of terminal buffers emit an OSC or DCS sequence libvterm does not handle. The TermRequest autocommand event has additional data in the v:termrequest variable. Co-authored-by: Gregory Anders <greg@gpanders.com>
This commit is contained in:
parent
f40df63bdc
commit
beca827212
@ -986,6 +986,11 @@ TermLeave After leaving |Terminal-mode|.
|
||||
TermClose When a |terminal| job ends.
|
||||
Sets these |v:event| keys:
|
||||
status
|
||||
*TermRequest*
|
||||
TermRequest When a |terminal| job emits an OSC or DCS
|
||||
sequence. Sets |v:termrequest|. When used from
|
||||
Lua, the request string is included in the
|
||||
"data" field of the autocommand callback.
|
||||
*TermResponse*
|
||||
TermResponse When Nvim receives an OSC or DCS response from
|
||||
the terminal. Sets |v:termresponse|. When used
|
||||
|
@ -284,6 +284,9 @@ The following new APIs and features were added.
|
||||
|
||||
• |vim.deepcopy()| has a `noref` argument to avoid hashing table values.
|
||||
|
||||
• Terminal buffers emit a |TermRequest| autocommand event when the child
|
||||
process emits an OSC or DCS control sequence.
|
||||
|
||||
==============================================================================
|
||||
CHANGED FEATURES *news-changed*
|
||||
|
||||
|
@ -647,9 +647,16 @@ v:t_number Value of |Number| type. Read-only. See: |type()|
|
||||
*v:t_string* *t_string-variable*
|
||||
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
|
||||
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.
|
||||
|
||||
*v:termresponse* *termresponse-variable*
|
||||
v:termresponse
|
||||
The value of the most recent OSC or DCS escape sequence
|
||||
The value of the most recent OSC or DCS control sequence
|
||||
received by Nvim from the terminal. This can be read in a
|
||||
|TermResponse| event handler after querying the terminal using
|
||||
another escape sequence.
|
||||
|
11
runtime/lua/vim/_meta/vvars.lua
generated
11
runtime/lua/vim/_meta/vvars.lua
generated
@ -687,11 +687,18 @@ vim.v.t_number = ...
|
||||
--- @type integer
|
||||
vim.v.t_string = ...
|
||||
|
||||
--- The value of the most recent OSC or DCS escape sequence
|
||||
--- The value of the most recent OSC or DCS 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.
|
||||
--- @type string
|
||||
vim.v.termrequest = ...
|
||||
|
||||
--- The value of the most recent OSC or DCS control sequence
|
||||
--- received by Nvim from the terminal. This can be read in a
|
||||
--- `TermResponse` event handler after querying the terminal using
|
||||
--- another escape sequence.
|
||||
--- @type any
|
||||
--- @type string
|
||||
vim.v.termresponse = ...
|
||||
|
||||
--- Must be set before using `test_garbagecollect_now()`.
|
||||
|
@ -109,6 +109,7 @@ return {
|
||||
'TermEnter', -- after entering Terminal mode
|
||||
'TermLeave', -- after leaving Terminal mode
|
||||
'TermOpen', -- after opening a terminal buffer
|
||||
'TermRequest', -- after an unhandled OSC sequence is emitted
|
||||
'TermResponse', -- after setting "v:termresponse"
|
||||
'TextChanged', -- text was modified
|
||||
'TextChangedI', -- text was modified in Insert mode(no popup)
|
||||
@ -166,6 +167,7 @@ return {
|
||||
TabNewEntered = true,
|
||||
TermClose = true,
|
||||
TermOpen = true,
|
||||
TermRequest = true,
|
||||
UIEnter = true,
|
||||
UILeave = true,
|
||||
},
|
||||
|
@ -185,6 +185,7 @@ static struct vimvar {
|
||||
VV(VV_VERSION, "version", VAR_NUMBER, VV_COMPAT + VV_RO),
|
||||
VV(VV_LNUM, "lnum", VAR_NUMBER, VV_RO_SBX),
|
||||
VV(VV_TERMRESPONSE, "termresponse", VAR_STRING, VV_RO),
|
||||
VV(VV_TERMREQUEST, "termrequest", VAR_STRING, VV_RO),
|
||||
VV(VV_FNAME, "fname", VAR_STRING, VV_RO),
|
||||
VV(VV_LANG, "lang", VAR_STRING, VV_RO),
|
||||
VV(VV_LC_TIME, "lc_time", VAR_STRING, VV_RO),
|
||||
|
@ -86,6 +86,7 @@ typedef enum {
|
||||
VV_THIS_SESSION,
|
||||
VV_VERSION,
|
||||
VV_LNUM,
|
||||
VV_TERMREQUEST,
|
||||
VV_TERMRESPONSE,
|
||||
VV_FNAME,
|
||||
VV_LANG,
|
||||
|
@ -169,6 +169,54 @@ static VTermScreenCallbacks vterm_screen_callbacks = {
|
||||
|
||||
static Set(ptr_t) invalidated_terminals = SET_INIT;
|
||||
|
||||
static void emit_term_request(void **argv)
|
||||
{
|
||||
char *payload = argv[0];
|
||||
size_t payload_length = (size_t)argv[1];
|
||||
|
||||
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, curbuf, NULL, &data);
|
||||
xfree(payload);
|
||||
}
|
||||
|
||||
static int on_osc(int command, VTermStringFragment frag, void *user)
|
||||
{
|
||||
if (frag.str == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
StringBuilder request = KV_INITIAL_VALUE;
|
||||
kv_printf(request, "\x1b]%d;", command);
|
||||
kv_concat_len(request, frag.str, frag.len);
|
||||
multiqueue_put(main_loop.events, emit_term_request, request.items, (void *)request.size);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user)
|
||||
{
|
||||
if ((command == NULL) || (frag.str == NULL)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
StringBuilder request = KV_INITIAL_VALUE;
|
||||
kv_printf(request, "\x1bP%*s", (int)commandlen, command);
|
||||
kv_concat_len(request, frag.str, frag.len);
|
||||
multiqueue_put(main_loop.events, emit_term_request, request.items, (void *)request.size);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static VTermStateFallbacks vterm_fallbacks = {
|
||||
.control = NULL,
|
||||
.csi = NULL,
|
||||
.osc = on_osc,
|
||||
.dcs = on_dcs,
|
||||
.apc = NULL,
|
||||
.pm = NULL,
|
||||
.sos = NULL,
|
||||
};
|
||||
|
||||
void terminal_init(void)
|
||||
{
|
||||
time_watcher_init(&main_loop, &refresh_timer, NULL);
|
||||
@ -222,6 +270,7 @@ void terminal_open(Terminal **termpp, buf_T *buf, TerminalOptions opts)
|
||||
vterm_screen_enable_reflow(rv->vts, true);
|
||||
// delete empty lines at the end of the buffer
|
||||
vterm_screen_set_callbacks(rv->vts, &vterm_screen_callbacks, rv);
|
||||
vterm_screen_set_unrecognised_fallbacks(rv->vts, &vterm_fallbacks, rv);
|
||||
vterm_screen_set_damage_merge(rv->vts, VTERM_DAMAGE_SCROLL);
|
||||
vterm_screen_reset(rv->vts, 1);
|
||||
vterm_output_set_callback(rv->vt, term_output_callback, rv);
|
||||
|
@ -770,13 +770,23 @@ M.vars = {
|
||||
desc = 'Value of |String| type. Read-only. See: |type()|',
|
||||
},
|
||||
termresponse = {
|
||||
type = 'string',
|
||||
desc = [=[
|
||||
The value of the most recent OSC or DCS escape sequence
|
||||
The value of the most recent OSC or DCS control sequence
|
||||
received by Nvim from the terminal. This can be read in a
|
||||
|TermResponse| event handler after querying the terminal using
|
||||
another escape sequence.
|
||||
]=],
|
||||
},
|
||||
termrequest = {
|
||||
type = 'string',
|
||||
desc = [=[
|
||||
The value of the most recent OSC or DCS 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.
|
||||
]=],
|
||||
},
|
||||
testing = {
|
||||
desc = [=[
|
||||
Must be set before using `test_garbagecollect_now()`.
|
||||
|
@ -317,6 +317,18 @@ describe(':terminal buffer', function()
|
||||
pcall_err(command, 'write test/functional/fixtures/tty-test.c')
|
||||
)
|
||||
end)
|
||||
|
||||
it('emits TermRequest events', function()
|
||||
command('split')
|
||||
command('enew')
|
||||
local term = meths.open_term(0, {})
|
||||
-- cwd will be inserted in a file URI, which cannot contain backs
|
||||
local cwd = funcs.getcwd():gsub('\\', '/')
|
||||
local parent = cwd:match('^(.+/)')
|
||||
local expected = '\027]7;file://host' .. parent
|
||||
meths.chan_send(term, string.format('%s\027\\', expected))
|
||||
eq(expected, eval('v:termrequest'))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('No heap-buffer-overflow when using', function()
|
||||
|
@ -28,6 +28,7 @@ local new_pipename = helpers.new_pipename
|
||||
local spawn_argv = helpers.spawn_argv
|
||||
local set_session = helpers.set_session
|
||||
local write_file = helpers.write_file
|
||||
local eval = helpers.eval
|
||||
|
||||
if helpers.skip(is_os('win')) then
|
||||
return
|
||||
@ -2736,6 +2737,42 @@ describe('TUI', function()
|
||||
unchanged = true,
|
||||
}
|
||||
end)
|
||||
|
||||
it('queries the terminal for truecolor support', function()
|
||||
clear()
|
||||
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
|
||||
vim.g.xtgettcap = true
|
||||
return true
|
||||
end
|
||||
end,
|
||||
})
|
||||
]])
|
||||
screen = thelpers.setup_child_nvim({
|
||||
'-u',
|
||||
'NONE',
|
||||
'-i',
|
||||
'NONE',
|
||||
}, {
|
||||
env = {
|
||||
VIMRUNTIME = os.getenv('VIMRUNTIME'),
|
||||
|
||||
-- Force COLORTERM to be unset and use a TERM that does not contain Tc or RGB in terminfo.
|
||||
-- This will force the nested nvim instance to query with XTGETTCAP
|
||||
COLORTERM = '',
|
||||
TERM = 'xterm-256colors',
|
||||
},
|
||||
})
|
||||
|
||||
retry(nil, 1000, function()
|
||||
eq(true, eval("get(g:, 'xtgettcap', v:false)"))
|
||||
eq(1, eval('&termguicolors'))
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('TUI bg color', function()
|
||||
@ -2743,6 +2780,18 @@ describe('TUI bg color', function()
|
||||
|
||||
local function setup_bg_test()
|
||||
clear()
|
||||
exec_lua([[
|
||||
vim.api.nvim_create_autocmd('TermRequest', {
|
||||
callback = function(args)
|
||||
local req = args.data
|
||||
if req == '\027]11;?' then
|
||||
vim.g.oscrequest = true
|
||||
return true
|
||||
end
|
||||
end,
|
||||
})
|
||||
]])
|
||||
|
||||
screen = thelpers.setup_child_nvim({
|
||||
'-u',
|
||||
'NONE',
|
||||
@ -2759,6 +2808,12 @@ describe('TUI bg color', function()
|
||||
|
||||
before_each(setup_bg_test)
|
||||
|
||||
it('queries the terminal for background color', function()
|
||||
retry(nil, 1000, function()
|
||||
eq(true, eval("get(g:, 'oscrequest', v:false)"))
|
||||
end)
|
||||
end)
|
||||
|
||||
it('triggers OptionSet event on unsplit terminal-response', function()
|
||||
screen:expect([[
|
||||
{1: } |
|
||||
|
Loading…
Reference in New Issue
Block a user