diff options
-rw-r--r-- | runtime/doc/autocmd.txt | 5 | ||||
-rw-r--r-- | runtime/doc/news.txt | 3 | ||||
-rw-r--r-- | runtime/doc/vvars.txt | 9 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/vvars.lua | 11 | ||||
-rw-r--r-- | src/nvim/auevents.lua | 2 | ||||
-rw-r--r-- | src/nvim/eval.c | 1 | ||||
-rw-r--r-- | src/nvim/eval.h | 1 | ||||
-rw-r--r-- | src/nvim/terminal.c | 49 | ||||
-rw-r--r-- | src/nvim/vvars.lua | 12 | ||||
-rw-r--r-- | test/functional/terminal/buffer_spec.lua | 12 | ||||
-rw-r--r-- | test/functional/terminal/tui_spec.lua | 55 |
11 files changed, 156 insertions, 4 deletions
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 45d7a4244c..ce3af01073 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -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 diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index d3ace5f33b..98b782a105 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -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* diff --git a/runtime/doc/vvars.txt b/runtime/doc/vvars.txt index 7e96d356d0..e71e31abf8 100644 --- a/runtime/doc/vvars.txt +++ b/runtime/doc/vvars.txt @@ -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. diff --git a/runtime/lua/vim/_meta/vvars.lua b/runtime/lua/vim/_meta/vvars.lua index e3b89aeff7..ca87fb9d15 100644 --- a/runtime/lua/vim/_meta/vvars.lua +++ b/runtime/lua/vim/_meta/vvars.lua @@ -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()`. diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 11d1597236..b931907fe3 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -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, }, diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 5069f00df0..451e258356 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -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), diff --git a/src/nvim/eval.h b/src/nvim/eval.h index c46a895c06..abe2685424 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -86,6 +86,7 @@ typedef enum { VV_THIS_SESSION, VV_VERSION, VV_LNUM, + VV_TERMREQUEST, VV_TERMRESPONSE, VV_FNAME, VV_LANG, diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index c6a2cb3354..ee482f7104 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -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); diff --git a/src/nvim/vvars.lua b/src/nvim/vvars.lua index d1b5e13401..6410df1a57 100644 --- a/src/nvim/vvars.lua +++ b/src/nvim/vvars.lua @@ -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()`. diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index 480ca96289..d0462b5619 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -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() diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index f6ee0f9c77..e65e57bc7f 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -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: } | |