Skip to content

Commit beca827

Browse files
glacambregpanders
andauthored
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>
1 parent f40df63 commit beca827

File tree

11 files changed

+156
-4
lines changed

11 files changed

+156
-4
lines changed

runtime/doc/autocmd.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,11 @@ TermLeave After leaving |Terminal-mode|.
986986
TermClose When a |terminal| job ends.
987987
Sets these |v:event| keys:
988988
status
989+
*TermRequest*
990+
TermRequest When a |terminal| job emits an OSC or DCS
991+
sequence. Sets |v:termrequest|. When used from
992+
Lua, the request string is included in the
993+
"data" field of the autocommand callback.
989994
*TermResponse*
990995
TermResponse When Nvim receives an OSC or DCS response from
991996
the terminal. Sets |v:termresponse|. When used

runtime/doc/news.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,9 @@ The following new APIs and features were added.
284284

285285
|vim.deepcopy()| has a `noref` argument to avoid hashing table values.
286286

287+
• Terminal buffers emit a |TermRequest| autocommand event when the child
288+
process emits an OSC or DCS control sequence.
289+
287290
==============================================================================
288291
CHANGED FEATURES *news-changed*
289292

runtime/doc/vvars.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -647,9 +647,16 @@ v:t_number Value of |Number| type. Read-only. See: |type()|
647647
*v:t_string* *t_string-variable*
648648
v:t_string Value of |String| type. Read-only. See: |type()|
649649

650+
*v:termrequest* *termrequest-variable*
651+
v:termrequest
652+
The value of the most recent OSC or DCS control sequence
653+
sent from a process running in the embedded |terminal|.
654+
This can be read in a |TermRequest| event handler to respond
655+
to queries from embedded applications.
656+
650657
*v:termresponse* *termresponse-variable*
651658
v:termresponse
652-
The value of the most recent OSC or DCS escape sequence
659+
The value of the most recent OSC or DCS control sequence
653660
received by Nvim from the terminal. This can be read in a
654661
|TermResponse| event handler after querying the terminal using
655662
another escape sequence.

runtime/lua/vim/_meta/vvars.lua

Lines changed: 9 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/nvim/auevents.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ return {
109109
'TermEnter', -- after entering Terminal mode
110110
'TermLeave', -- after leaving Terminal mode
111111
'TermOpen', -- after opening a terminal buffer
112+
'TermRequest', -- after an unhandled OSC sequence is emitted
112113
'TermResponse', -- after setting "v:termresponse"
113114
'TextChanged', -- text was modified
114115
'TextChangedI', -- text was modified in Insert mode(no popup)
@@ -166,6 +167,7 @@ return {
166167
TabNewEntered = true,
167168
TermClose = true,
168169
TermOpen = true,
170+
TermRequest = true,
169171
UIEnter = true,
170172
UILeave = true,
171173
},

src/nvim/eval.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ static struct vimvar {
185185
VV(VV_VERSION, "version", VAR_NUMBER, VV_COMPAT + VV_RO),
186186
VV(VV_LNUM, "lnum", VAR_NUMBER, VV_RO_SBX),
187187
VV(VV_TERMRESPONSE, "termresponse", VAR_STRING, VV_RO),
188+
VV(VV_TERMREQUEST, "termrequest", VAR_STRING, VV_RO),
188189
VV(VV_FNAME, "fname", VAR_STRING, VV_RO),
189190
VV(VV_LANG, "lang", VAR_STRING, VV_RO),
190191
VV(VV_LC_TIME, "lc_time", VAR_STRING, VV_RO),

src/nvim/eval.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ typedef enum {
8686
VV_THIS_SESSION,
8787
VV_VERSION,
8888
VV_LNUM,
89+
VV_TERMREQUEST,
8990
VV_TERMRESPONSE,
9091
VV_FNAME,
9192
VV_LANG,

src/nvim/terminal.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,54 @@ static VTermScreenCallbacks vterm_screen_callbacks = {
169169

170170
static Set(ptr_t) invalidated_terminals = SET_INIT;
171171

172+
static void emit_term_request(void **argv)
173+
{
174+
char *payload = argv[0];
175+
size_t payload_length = (size_t)argv[1];
176+
177+
String termrequest = { .data = payload, .size = payload_length };
178+
Object data = STRING_OBJ(termrequest);
179+
set_vim_var_string(VV_TERMREQUEST, payload, (ptrdiff_t)payload_length);
180+
apply_autocmds_group(EVENT_TERMREQUEST, NULL, NULL, false, AUGROUP_ALL, curbuf, NULL, &data);
181+
xfree(payload);
182+
}
183+
184+
static int on_osc(int command, VTermStringFragment frag, void *user)
185+
{
186+
if (frag.str == NULL) {
187+
return 0;
188+
}
189+
190+
StringBuilder request = KV_INITIAL_VALUE;
191+
kv_printf(request, "\x1b]%d;", command);
192+
kv_concat_len(request, frag.str, frag.len);
193+
multiqueue_put(main_loop.events, emit_term_request, request.items, (void *)request.size);
194+
return 1;
195+
}
196+
197+
static int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user)
198+
{
199+
if ((command == NULL) || (frag.str == NULL)) {
200+
return 0;
201+
}
202+
203+
StringBuilder request = KV_INITIAL_VALUE;
204+
kv_printf(request, "\x1bP%*s", (int)commandlen, command);
205+
kv_concat_len(request, frag.str, frag.len);
206+
multiqueue_put(main_loop.events, emit_term_request, request.items, (void *)request.size);
207+
return 1;
208+
}
209+
210+
static VTermStateFallbacks vterm_fallbacks = {
211+
.control = NULL,
212+
.csi = NULL,
213+
.osc = on_osc,
214+
.dcs = on_dcs,
215+
.apc = NULL,
216+
.pm = NULL,
217+
.sos = NULL,
218+
};
219+
172220
void terminal_init(void)
173221
{
174222
time_watcher_init(&main_loop, &refresh_timer, NULL);
@@ -222,6 +270,7 @@ void terminal_open(Terminal **termpp, buf_T *buf, TerminalOptions opts)
222270
vterm_screen_enable_reflow(rv->vts, true);
223271
// delete empty lines at the end of the buffer
224272
vterm_screen_set_callbacks(rv->vts, &vterm_screen_callbacks, rv);
273+
vterm_screen_set_unrecognised_fallbacks(rv->vts, &vterm_fallbacks, rv);
225274
vterm_screen_set_damage_merge(rv->vts, VTERM_DAMAGE_SCROLL);
226275
vterm_screen_reset(rv->vts, 1);
227276
vterm_output_set_callback(rv->vt, term_output_callback, rv);

src/nvim/vvars.lua

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -770,13 +770,23 @@ M.vars = {
770770
desc = 'Value of |String| type. Read-only. See: |type()|',
771771
},
772772
termresponse = {
773+
type = 'string',
773774
desc = [=[
774-
The value of the most recent OSC or DCS escape sequence
775+
The value of the most recent OSC or DCS control sequence
775776
received by Nvim from the terminal. This can be read in a
776777
|TermResponse| event handler after querying the terminal using
777778
another escape sequence.
778779
]=],
779780
},
781+
termrequest = {
782+
type = 'string',
783+
desc = [=[
784+
The value of the most recent OSC or DCS control sequence
785+
sent from a process running in the embedded |terminal|.
786+
This can be read in a |TermRequest| event handler to respond
787+
to queries from embedded applications.
788+
]=],
789+
},
780790
testing = {
781791
desc = [=[
782792
Must be set before using `test_garbagecollect_now()`.

test/functional/terminal/buffer_spec.lua

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,18 @@ describe(':terminal buffer', function()
317317
pcall_err(command, 'write test/functional/fixtures/tty-test.c')
318318
)
319319
end)
320+
321+
it('emits TermRequest events', function()
322+
command('split')
323+
command('enew')
324+
local term = meths.open_term(0, {})
325+
-- cwd will be inserted in a file URI, which cannot contain backs
326+
local cwd = funcs.getcwd():gsub('\\', '/')
327+
local parent = cwd:match('^(.+/)')
328+
local expected = '\027]7;file://host' .. parent
329+
meths.chan_send(term, string.format('%s\027\\', expected))
330+
eq(expected, eval('v:termrequest'))
331+
end)
320332
end)
321333

322334
describe('No heap-buffer-overflow when using', function()

0 commit comments

Comments
 (0)