diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 4430d97fc7..edf683473f 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -3591,6 +3591,21 @@ nvim_ui_set_option({name}, {value}) *nvim_ui_set_option()* Attributes: ~ |RPC| only +nvim_ui_term_event({event}, {value}) *nvim_ui_term_event()* + Tells Nvim when a terminal event has occurred. + + The following terminal events are supported: + + • "osc_response": The terminal sent a OSC response sequence to Nvim. The + payload is the received OSC sequence. + + Attributes: ~ + |RPC| only + + Parameters: ~ + • {event} Event name + • {payload} Event payload + nvim_ui_try_resize({width}, {height}) *nvim_ui_try_resize()* TODO: Documentation diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 4b36a7d4ec..6b698b0868 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -987,12 +987,25 @@ TermClose When a |terminal| job ends. Sets these |v:event| keys: status *TermResponse* -TermResponse After the response to t_RV is received from - the terminal. The value of |v:termresponse| - can be used to do things depending on the - terminal version. May be triggered halfway - through another event (file I/O, a shell - command, or anything else that takes time). +TermResponse When Nvim receives a OSC response from the + terminal. Sets |v:termresponse|. When used + from Lua, the response string is included in + the "data" field of the autocommand callback. + May be triggered halfway through another event + (file I/O, a shell command, or anything else + that takes time). Example: >lua + + -- Query the terminal palette for the RGB value of color 1 + -- (red) using OSC 4 + vim.api.nvim_create_autocmd('TermResponse', { + once = true, + callback = function(args) + local resp = args.data + local r, g, b = resp:match("\x1b%]4;1;rgb:(%w+)/(%w+)/(%w+)") + end, + }) + io.stdout:write("\x1b]4;1;?\x1b\\") +< *TextChanged* TextChanged After a change was made to the text in the current buffer in Normal mode. That is after diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 4cb29205a5..2223829548 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2318,18 +2318,10 @@ v:t_string Value of |String| type. Read-only. See: |type()| v:t_blob Value of |Blob| type. Read-only. See: |type()| *v:termresponse* *termresponse-variable* -v:termresponse The escape sequence returned by the terminal for the DA - (request primary device attributes) control sequence. It is - set when Vim receives an escape sequence that starts with ESC - [ or CSI and ends in a 'c', with only digits, ';' and '.' in - between. - When this option is set, the TermResponse autocommand event is - fired, so that you can react to the response from the - terminal. - The response from a new xterm is: "[ Pp ; Pv ; Pc c". Pp - is the terminal type: 0 for vt100 and 1 for vt220. Pv is the - patch level (since this was introduced in patch 95, it's - always 95 or bigger). Pc is always zero. +v:termresponse The value of the most recent OSC escape sequence received by + Nvim from the terminal. This can be read in a |TermResponse| + event handler after querying the terminal using another escape + sequence. *v:testing* *testing-variable* v:testing Must be set before using `test_garbagecollect_now()`. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index e32e1aadb6..fa77e8d086 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -205,6 +205,9 @@ The following new APIs and features were added. • Added |vim.base64.encode()| and |vim.base64.decode()| for encoding and decoding strings using Base64 encoding. +• The |TermResponse| autocommand event can be used with |v:termresponse| to + read escape sequence responses from the terminal. + ============================================================================== CHANGED FEATURES *news-changed* diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 8249179187..efebf46d85 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -567,6 +567,8 @@ Working directory (Vim implemented some of these after Nvim): Autocommands: - Fixed inconsistent behavior in execution of nested autocommands: https://github.com/neovim/neovim/issues/23368 +- |TermResponse| is fired for any OSC sequence received from the terminal, + instead of the Primary Device Attributes response. |v:termresponse| ============================================================================== Missing features *nvim-missing* diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 691da62f4f..0bee27970d 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -2059,6 +2059,17 @@ function vim.api.nvim_ui_set_focus(gained) end --- @param value any function vim.api.nvim_ui_set_option(name, value) end +--- Tells Nvim when a terminal event has occurred. +--- The following terminal events are supported: +--- +--- • "osc_response": The terminal sent a OSC response sequence to Nvim. The +--- payload is the received OSC sequence. +--- +--- +--- @param event string Event name +--- @param value any +function vim.api.nvim_ui_term_event(event, value) end + --- @param width integer --- @param height integer function vim.api.nvim_ui_try_resize(width, height) end diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index b508a3ee94..99215f7b4f 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -16,6 +16,7 @@ #include "nvim/api/ui.h" #include "nvim/autocmd.h" #include "nvim/channel.h" +#include "nvim/eval.h" #include "nvim/event/loop.h" #include "nvim/event/wstream.h" #include "nvim/globals.h" @@ -524,6 +525,32 @@ void nvim_ui_pum_set_bounds(uint64_t channel_id, Float width, Float height, Floa ui->pum_pos = true; } +/// Tells Nvim when a terminal event has occurred. +/// +/// The following terminal events are supported: +/// +/// - "osc_response": The terminal sent a OSC response sequence to Nvim. The +/// payload is the received OSC sequence. +/// +/// @param channel_id +/// @param event Event name +/// @param payload Event payload +/// @param[out] err Error details, if any. +void nvim_ui_term_event(uint64_t channel_id, String event, Object value, Error *err) + FUNC_API_SINCE(12) FUNC_API_REMOTE_ONLY +{ + if (strequal("osc_response", event.data)) { + if (value.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, "osc_response must be a string"); + return; + } + + const String osc_response = value.data.string; + set_vim_var_string(VV_TERMRESPONSE, osc_response.data, (ptrdiff_t)osc_response.size); + apply_autocmds_group(EVENT_TERMRESPONSE, NULL, NULL, false, AUGROUP_ALL, NULL, NULL, &value); + } +} + static void flush_event(UIData *data) { if (data->cur_event) { diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 487f8b010e..1f659264c5 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -11,6 +11,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/charset.h" +#include "nvim/eval.h" #include "nvim/event/defs.h" #include "nvim/log.h" #include "nvim/macros.h" @@ -479,6 +480,8 @@ static void tk_getkeys(TermInput *input, bool force) } } } + } else if (key.type == TERMKEY_TYPE_OSC) { + handle_osc_event(input, &key); } } @@ -684,6 +687,29 @@ HandleState handle_background_color(TermInput *input) return kComplete; } +static void handle_osc_event(TermInput *input, const TermKeyKey *key) +{ + assert(input); + + const char *str = NULL; + if (termkey_interpret_string(input->tk, key, &str) == TERMKEY_RES_KEY) { + assert(str != NULL); + + // Send an event to nvim core. This will update the v:termresponse variable and fire the + // TermResponse event + MAXSIZE_TEMP_ARRAY(args, 2); + ADD_C(args, STATIC_CSTR_AS_OBJ("osc_response")); + + // libtermkey strips the OSC bytes from the response. We add it back in so that downstream + // consumers of v:termresponse can differentiate between OSC and CSI events. + StringBuilder response = KV_INITIAL_VALUE; + kv_printf(response, "\x1b]%s", str); + ADD_C(args, STRING_OBJ(cbuf_as_string(response.items, response.size))); + rpc_send_event(ui_client_channel_id, "nvim_ui_term_event", args); + kv_destroy(response); + } +} + static void handle_raw_buffer(TermInput *input, bool force) { HandleState is_paste = kNotApplicable;