From d9ed4c8566c5d77a1e2a2edfe21259e6f68f62ae Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 30 Apr 2026 07:26:40 -0400 Subject: [PATCH] refactor(tty): tty.request() #39489 Problem: - Various `TermRequest` handlers which all do similar things. - `tty.query` is specific to `XTGETTCAP DCS`, can't be reused for other kinds of terminal queries. Solution: Provide `tty.request()`. --- runtime/lua/vim/_core/defaults.lua | 197 ++++++++---------- runtime/lua/vim/_init_packages.lua | 1 + runtime/lua/vim/_meta.lua | 1 + runtime/lua/vim/tty.lua | 197 ++++++++++-------- runtime/lua/vim/ui/clipboard/osc52.lua | 26 +-- runtime/lua/vim/ui/img/_kitty.lua | 2 +- .../pack/dist/opt/nvim.tohtml/lua/tohtml.lua | 36 ++-- runtime/plugin/osc52.lua | 2 +- src/gen/gen_steps.zig | 1 + src/nvim/CMakeLists.txt | 3 + test/functional/core/main_spec.lua | 1 + 11 files changed, 223 insertions(+), 244 deletions(-) diff --git a/runtime/lua/vim/_core/defaults.lua b/runtime/lua/vim/_core/defaults.lua index 4dfe0cd057..7313dc9871 100644 --- a/runtime/lua/vim/_core/defaults.lua +++ b/runtime/lua/vim/_core/defaults.lua @@ -914,57 +914,55 @@ do return nil, nil, nil end - -- This autocommand updates the value of 'background' anytime we receive - -- an OSC 11 response from the terminal emulator. If the user has set - -- 'background' explicitly then we will delete this autocommand, - -- effectively disabling automatic background setting. + -- Send OSC 11 query along with DSR sequence to determine whether terminal supports the query. + -- If the DSR response comes first, the terminal most likely doesn't support the bg color + -- query, and we don't have to keep waiting for a bg color response. #32109 + local osc11 = '\027]11;?\007' + local dsr = '\027[5n' + + -- This handler updates the value of 'background' anytime we receive an OSC 11 response from + -- the terminal emulator. If the user has set 'background' explicitly then we will delete the + -- autocommand below, effectively disabling automatic background setting. local did_dsr_response = false - local id = vim.api.nvim_create_autocmd('TermResponse', { - group = group, - nested = true, - desc = "Update the value of 'background' automatically based on the terminal emulator's background color", - callback = function(ev) - local resp = ev.data.sequence ---@type string + -- VimEnter handles cleanup; no built-in timeout. + local id = vim.tty.request(osc11 .. dsr, { group = group, timeout = 0 }, function(resp) + -- DSR response that should come after the OSC 11 response if the + -- terminal supports it. + if string.match(resp, '^\027%[0n$') then + did_dsr_response = true + -- Don't stop listening: the bg response may come after the DSR response if the terminal + -- handles requests out of sequence. In that case, the bg will simply be set later in the + -- startup sequence. + return + end - -- DSR response that should come after the OSC 11 response if the - -- terminal supports it. - if string.match(resp, '^\027%[0n$') then - did_dsr_response = true - -- Don't delete the autocmd because the bg response may come - -- after the DSR response if the terminal handles requests out - -- of sequence. In that case, the background will simply be set - -- later in the startup sequence. - return false - end + local r, g, b = parseosc11(resp) + if r and g and b then + local rr = parsecolor(r) + local gg = parsecolor(g) + local bb = parsecolor(b) - local r, g, b = parseosc11(resp) - if r and g and b then - local rr = parsecolor(r) - local gg = parsecolor(g) - local bb = parsecolor(b) + if rr and gg and bb then + local luminance = (0.299 * rr) + (0.587 * gg) + (0.114 * bb) + local bg = luminance < 0.5 and 'dark' or 'light' + vim.api.nvim_set_option_value('background', bg, {}) - if rr and gg and bb then - local luminance = (0.299 * rr) + (0.587 * gg) + (0.114 * bb) - local bg = luminance < 0.5 and 'dark' or 'light' - vim.api.nvim_set_option_value('background', bg, {}) - - -- Ensure OptionSet still triggers when we set the background during startup - if vim.v.vim_did_enter == 0 then - vim.api.nvim_create_autocmd('VimEnter', { - group = group, - once = true, - nested = true, - callback = function() - vim.api.nvim_exec_autocmds('OptionSet', { - pattern = 'background', - }) - end, - }) - end + -- Ensure OptionSet still triggers when we set the background during startup + if vim.v.vim_did_enter == 0 then + vim.api.nvim_create_autocmd('VimEnter', { + group = group, + once = true, + nested = true, + callback = function() + vim.api.nvim_exec_autocmds('OptionSet', { + pattern = 'background', + }) + end, + }) end end - end, - }) + end + end) vim.api.nvim_create_autocmd('VimEnter', { group = group, @@ -983,15 +981,6 @@ do end, }) - -- Send OSC 11 query along with DSR sequence to determine whether - -- terminal supports the query. If the DSR response comes first, - -- the terminal most likely doesn't support the bg color query, - -- and we don't have to keep waiting for a bg color response. - -- #32109 - local osc11 = '\027]11;?\007' - local dsr = '\027[5n' - vim.api.nvim_ui_send(osc11 .. dsr) - -- Wait until detection of OSC 11 capabilities is complete to -- ensure background is automatically set before user config. if @@ -1024,7 +1013,7 @@ do -- Neither the TUI nor $COLORTERM indicate that truecolor is supported, so query the -- terminal local caps = {} ---@type table - require('vim.tty').query({ 'Tc', 'RGB', 'setrgbf', 'setrgbb' }, function(cap, found) + vim.tty.query({ 'Tc', 'RGB', 'setrgbf', 'setrgbb' }, function(cap, found) if not found then return end @@ -1035,77 +1024,57 @@ do end end) - local timer = assert(vim.uv.new_timer()) - -- Arbitrary colors to set in the SGR sequence local r = 1 local g = 2 local b = 3 - local id = vim.api.nvim_create_autocmd('TermResponse', { - group = group, - nested = true, - callback = function(ev) - local resp = ev.data.sequence ---@type string - local decrqss = resp:match('^\027P1%$r([%d;:]+)m$') - - if decrqss then - -- The DECRQSS SGR response first contains attributes separated by - -- semicolons, followed by the SGR itself with parameters separated - -- by colons. Some terminals include "0" in the attribute list - -- unconditionally; others do not. Our SGR sequence did not set any - -- attributes, so there should be no attributes in the list. - local attrs = vim.split(decrqss, ';') - if #attrs ~= 1 and (#attrs ~= 2 or attrs[1] ~= '0') then - return false - end - - -- The returned SGR sequence should begin with 48:2 - local sgr = assert(attrs[#attrs]):match('^48:2:([%d:]+)$') - if not sgr then - return false - end - - -- The remaining elements of the SGR sequence should be the 3 colors - -- we set. Some terminals also include an additional parameter - -- (which can even be empty!), so handle those cases as well - local params = vim.split(sgr, ':') - if #params ~= 3 and (#params ~= 4 or (params[1] ~= '' and params[1] ~= '1')) then - return true - end - - if - vim._tointeger(params[#params - 2]) == r - and vim._tointeger(params[#params - 1]) == g - and vim._tointeger(params[#params]) == b - then - setoption('termguicolors', true) - end - - return true - end - end, - }) - -- Write SGR followed by DECRQSS. This sets the background color then -- immediately asks the terminal what the background color is. If the -- terminal responds to the DECRQSS with the same SGR sequence that we -- sent then the terminal supports truecolor. - local decrqss = '\027P$qm\027\\' - + -- -- Reset attributes first, as other code may have set attributes. - vim.api.nvim_ui_send(string.format('\027[0m\027[48;2;%d;%d;%dm%s', r, g, b, decrqss)) + local payload = ('\027[0m\027[48;2;%d;%d;%dm%s'):format(r, g, b, '\027P$qm\027\\') - timer:start(1000, 0, function() - -- Delete the autocommand if no response was received - vim.schedule(function() - -- Suppress error if autocommand has already been deleted - pcall(vim.api.nvim_del_autocmd, id) - end) - - if not timer:is_closing() then - timer:close() + vim.tty.request(payload, { group = group }, function(resp) + local decrqss = resp:match('^\027P1%$r([%d;:]+)m$') + if not decrqss then + return end + + -- The DECRQSS SGR response first contains attributes separated by semicolons, followed by + -- the SGR itself with parameters separated by colons. Some terminals include "0" in the + -- attribute list unconditionally; others do not. Our SGR sequence did not set any + -- attributes, so there should be no attributes in the list. + local attrs = vim.split(decrqss, ';') + if #attrs ~= 1 and (#attrs ~= 2 or attrs[1] ~= '0') then + return + end + + -- The returned SGR sequence should begin with 48:2 + local sgr = assert(attrs[#attrs]):match('^48:2:([%d:]+)$') + if not sgr then + return + end + + -- The remaining elements of the SGR sequence should be the 3 colors we set. Some + -- terminals also include an additional parameter (which can even be empty!), so handle + -- those cases as well + local params = vim.split(sgr, ':') + if #params ~= 3 and (#params ~= 4 or (params[1] ~= '' and params[1] ~= '1')) then + return true + end + + if + vim._tointeger(params[#params - 2]) == r + and vim._tointeger(params[#params - 1]) == g + and vim._tointeger(params[#params]) == b + then + setoption('termguicolors', true) + end + + return true end) end end diff --git a/runtime/lua/vim/_init_packages.lua b/runtime/lua/vim/_init_packages.lua index b5c0a0f603..5108a9fe70 100644 --- a/runtime/lua/vim/_init_packages.lua +++ b/runtime/lua/vim/_init_packages.lua @@ -60,6 +60,7 @@ vim._submodules = { iter = true, re = true, text = true, + tty = true, provider = true, } diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index 3c8947751d..6ffe21a702 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -31,6 +31,7 @@ vim.secure = require('vim.secure') vim.snippet = require('vim.snippet') vim.text = require('vim.text') vim.treesitter = require('vim.treesitter') +vim.tty = require('vim.tty') vim.ui = require('vim.ui') vim.version = require('vim.version') diff --git a/runtime/lua/vim/tty.lua b/runtime/lua/vim/tty.lua index 6f624309dd..b70f4f7887 100644 --- a/runtime/lua/vim/tty.lua +++ b/runtime/lua/vim/tty.lua @@ -1,5 +1,67 @@ local M = {} +--- Send `payload` to the host terminal and listen for `TermResponse`, calling `on_response` for +--- each response. Cleans up after `opts.timeout` ms if the callback never returns `true`. +--- +--- The autocommand is removed when: +--- - `on_response()` returns `true` +--- - the timeout fires (and `opts.on_timeout` is called, if given) +--- - the caller explicitly deletes the returned autocmd id +--- +---@param payload string Sequence to send via nvim_ui_send(). Use empty string ('') to just register +--- a listener (no sending). +---@param opts? { timeout?: integer, on_timeout?: fun(), group?: integer|string } +--- - `timeout` (default: 1000) ms to wait before giving up, or 0 for never (caller must remove the autocmd). +--- - `on_timeout` optional fn called when the timeout fires. +--- - `group`: augroup for the TermResponse autocmd. +---@param on_response fun(resp:string):boolean? Called for each TermResponse. Return `true` to stop listening. +---@return integer # autocmd id of the TermResponse handler. +function M.request(payload, opts, on_response) + vim.validate('payload', payload, 'string') + vim.validate('opts', opts, 'table', true) + vim.validate('on_response', on_response, 'function') + + opts = opts or {} + local timeout = opts.timeout or 1000 + local timer ---@type uv.uv_timer_t? + if timeout > 0 then + timer = assert(vim.uv.new_timer()) + end + + local id = vim.api.nvim_create_autocmd('TermResponse', { + group = opts.group, + nested = true, + callback = function(ev) + local stop = on_response(ev.data.sequence) + -- If on_response is done, cancel the timeout so on_timeout doesn't fire spuriously. + if stop and timer and not timer:is_closing() then + timer:close() + end + return stop + end, + }) + + if payload ~= '' then + vim.api.nvim_ui_send(payload) + end + + if timer then + timer:start(timeout, 0, function() + vim.schedule(function() + pcall(vim.api.nvim_del_autocmd, id) + if opts.on_timeout then + opts.on_timeout() + end + end) + if not timer:is_closing() then + timer:close() + end + end) + end + + return id +end + --- Query the host terminal emulator for terminfo capabilities. --- --- This function sends the XTGETTCAP DCS sequence to the host terminal emulator asking the terminal @@ -12,13 +74,12 @@ local M = {} --- emulator supports the XTGETTCAP sequence. --- --- @param caps string|table A terminal capability or list of capabilities to query ---- @param cb fun(cap:string, found:boolean, seq:string?) Callback function which is called for ---- each capability in {caps}. {found} is set to true if the capability was found or false ---- otherwise. {seq} is the control sequence for the capability if found, or nil for ---- boolean capabilities. -function M.query(caps, cb) +--- @param on_response fun(cap:string, found:boolean, seq:string?) Called for each capability in +--- `caps`. `found` is true if the capability was found, else false. `seq` is the control +--- sequence if found, or nil for boolean capabilities. +function M.query(caps, on_response) vim.validate('caps', caps, { 'string', 'table' }) - vim.validate('cb', cb, 'function') + vim.validate('on_response', on_response, 'function') if type(caps) ~= 'table' then caps = { caps } @@ -29,112 +90,70 @@ function M.query(caps, cb) pending[v] = true end - local timer = assert(vim.uv.new_timer()) - - local id = vim.api.nvim_create_autocmd('TermResponse', { - nested = true, - callback = function(ev) - local resp = ev.data.sequence ---@type string - local k, rest = resp:match('^\027P1%+r(%x+)(.*)$') - if k and rest then - local cap = vim.text.hexdecode(k) - if not cap or not pending[cap] then - -- Received a response for a capability we didn't request. This can happen if there are - -- multiple concurrent XTGETTCAP requests - return - end - - local seq ---@type string? - if rest:match('^=%x+$') then - seq = vim.text - .hexdecode(rest:sub(2)) - :gsub('\\E', '\027') - :gsub('%%p%d', '') - :gsub('\\(%d+)', string.char) - end - - cb(cap, true, seq) - - pending[cap] = nil - - if next(pending) == nil then - return true - end - end - end, - }) - local encoded = {} ---@type string[] for i = 1, #caps do encoded[i] = vim.text.hexencode(caps[i]) end + local payload = ('\027P+q%s\027\\'):format(table.concat(encoded, ';')) - local query = string.format('\027P+q%s\027\\', table.concat(encoded, ';')) - - vim.api.nvim_ui_send(query) - - timer:start(1000, 0, function() - -- Delete the autocommand if no response was received - vim.schedule(function() - -- Suppress error if autocommand has already been deleted - pcall(vim.api.nvim_del_autocmd, id) - - -- Call the callback for all capabilities that were not found + M.request(payload, { + on_timeout = function() + -- Call the callback for all capabilities that were not found. for k in pairs(pending) do - cb(k, false, nil) + on_response(k, false, nil) end - end) - - if not timer:is_closing() then - timer:close() + end, + }, function(resp) + local k, rest = resp:match('^\027P1%+r(%x+)(.*)$') + if not k or not rest then + return end + local cap = vim.text.hexdecode(k) + if not cap or not pending[cap] then + -- Received a response for a capability we didn't request. This can happen if there are + -- multiple concurrent XTGETTCAP requests + return + end + + local seq ---@type string? + if rest:match('^=%x+$') then + seq = vim.text + .hexdecode(rest:sub(2)) + :gsub('\\E', '\027') + :gsub('%%p%d', '') + :gsub('\\(%d+)', string.char) + end + + on_response(cap, true, seq) + pending[cap] = nil + + return next(pending) == nil end) end ---- Send an APC sequence to the terminal and call {cb} for each APC response received. +--- Send an APC sequence to the terminal and call `on_response` for each APC response received. --- Cleans up after {timeout} milliseconds if no response is received. --- ---- {cb} receives the full APC sequence including the `\027_` prefix. ---- Return `true` from {cb} to stop listening. +--- `on_response` receives the full APC sequence including the `\027_` prefix. +--- Return `true` from `on_response` to stop listening. --- ---@param payload string APC sequence to send (full escape sequence including prefix/suffix) ---@param opts {timeout?:integer} Options table (timeout in milliseconds, default 1000) ----@param cb fun(resp:string):boolean? Callback invoked for each APC TermResponse ----@overload fun(payload:string, cb:fun(resp:string):boolean?) -function M.query_apc(payload, opts, cb) +---@param on_response fun(resp:string):boolean? Callback invoked for each APC TermResponse +---@overload fun(payload:string, on_response:fun(resp:string):boolean?) +function M.query_apc(payload, opts, on_response) if type(opts) == 'function' then - cb = opts + on_response = opts opts = {} end vim.validate('payload', payload, 'string') vim.validate('opts', opts, 'table') - vim.validate('cb', cb, 'function') + vim.validate('on_response', on_response, 'function') - local timeout = opts and opts.timeout or 1000 - local timer = assert(vim.uv.new_timer()) - - local id = vim.api.nvim_create_autocmd('TermResponse', { - nested = true, - callback = function(ev) - local resp = ev.data.sequence ---@type string - if resp:match('^\027_') then - if not timer:is_closing() then - timer:close() - end - return cb(resp) - end - end, - }) - - vim.api.nvim_ui_send(payload) - - timer:start(timeout, 0, function() - vim.schedule(function() - pcall(vim.api.nvim_del_autocmd, id) - end) - if not timer:is_closing() then - timer:close() + M.request(payload, opts, function(resp) + if resp:match('^\027_') then + return on_response(resp) end end) end diff --git a/runtime/lua/vim/ui/clipboard/osc52.lua b/runtime/lua/vim/ui/clipboard/osc52.lua index b0164accea..e37b0c2be8 100644 --- a/runtime/lua/vim/ui/clipboard/osc52.lua +++ b/runtime/lua/vim/ui/clipboard/osc52.lua @@ -22,23 +22,17 @@ function M.paste(reg) local clipboard = reg == '+' and 'c' or 'p' return function() local contents = nil --- @type string? - local id = vim.api.nvim_create_autocmd('TermResponse', { - callback = function(ev) - local resp = ev.data.sequence ---@type string - local encoded = resp:match('\027%]52;%w?;([A-Za-z0-9+/=]*)') - if encoded then - contents = vim.base64.decode(encoded) - return true - end - end, - }) - - vim.api.nvim_ui_send(osc52(clipboard, '?')) - - local ok, res + -- timeout=0: we manage the staged 1s/9s timeout via vim.wait below. + local id = vim.tty.request(osc52(clipboard, '?'), { timeout = 0 }, function(resp) + local encoded = resp:match('\027%]52;%w?;([A-Za-z0-9+/=]*)') + if encoded then + contents = vim.base64.decode(encoded) + return true + end + end) -- Wait 1s first for terminals that respond quickly - ok, res = vim.wait(1000, function() + local ok, res = vim.wait(1000, function() return contents ~= nil end) @@ -55,7 +49,7 @@ function M.paste(reg) end if not ok then - vim.api.nvim_del_autocmd(id) + pcall(vim.api.nvim_del_autocmd, id) if res == -1 then vim.notify( 'Timed out waiting for a clipboard response from the terminal', diff --git a/runtime/lua/vim/ui/img/_kitty.lua b/runtime/lua/vim/ui/img/_kitty.lua index cd4814044b..33e1f4236a 100644 --- a/runtime/lua/vim/ui/img/_kitty.lua +++ b/runtime/lua/vim/ui/img/_kitty.lua @@ -178,7 +178,7 @@ function M.supported(opts) ---@type string? local msg - require('vim.tty').query_apc( + vim.tty.query_apc( seq({ a = 'q', i = query_id, s = 1, v = 1 }), { timeout = timeout }, function(resp) diff --git a/runtime/pack/dist/opt/nvim.tohtml/lua/tohtml.lua b/runtime/pack/dist/opt/nvim.tohtml/lua/tohtml.lua index 767997809e..d509a3d036 100644 --- a/runtime/pack/dist/opt/nvim.tohtml/lua/tohtml.lua +++ b/runtime/pack/dist/opt/nvim.tohtml/lua/tohtml.lua @@ -197,32 +197,22 @@ local len = vim.api.nvim_strwidth --- @param color "background"|"foreground"|integer --- @return string? local function try_query_terminal_color(color) - local parameter = 4 - if color == 'foreground' then - parameter = 10 - elseif color == 'background' then - parameter = 11 - end + local parameter = (color == 'foreground' and 10) or (color == 'background' and 11) or 4 + local payload = type(color) == 'number' and ('\027]%s;%s;?\027\\'):format(parameter, color) + or ('\027]%s;?\027\\'):format(parameter) + --- @type string? local hex = nil - local au = vim.api.nvim_create_autocmd('TermResponse', { - once = true, - callback = function(ev) - hex = '#' - .. table.concat({ - ev.data.sequence:match('\027%]%d+;%d*;?rgb:(%w%w)%w%w/(%w%w)%w%w/(%w%w)%w%w'), - }) - end, - }) - if type(color) == 'number' then - vim.api.nvim_ui_send(('\027]%s;%s;?\027\\'):format(parameter, color)) - else - vim.api.nvim_ui_send(('\027]%s;?\027\\'):format(parameter)) - end - vim.wait(100, function() - return hex and true or false + vim.tty.request(payload, { timeout = 100 }, function(resp) + local r, g, b = resp:match('\027%]%d+;%d*;?rgb:(%w%w)%w%w/(%w%w)%w%w/(%w%w)%w%w') ---@type string?, string?, string? + if r and g and b then + hex = '#' .. r .. g .. b + return true + end + end) + vim.wait(100, function() + return hex ~= nil end) - pcall(vim.api.nvim_del_autocmd, au) return hex end diff --git a/runtime/plugin/osc52.lua b/runtime/plugin/osc52.lua index deeda7ad31..fbd6a2084b 100644 --- a/runtime/plugin/osc52.lua +++ b/runtime/plugin/osc52.lua @@ -60,7 +60,7 @@ vim.api.nvim_create_autocmd('UIEnter', { end -- Fallback to XTGETTCAP - require('vim.tty').query('Ms', function(cap, found, seq) + vim.tty.query('Ms', function(cap, found, seq) if not found then return end diff --git a/src/gen/gen_steps.zig b/src/gen/gen_steps.zig index 11e14c3d63..a791ad62b1 100644 --- a/src/gen/gen_steps.zig +++ b/src/gen/gen_steps.zig @@ -78,6 +78,7 @@ pub fn nvim_gen_sources( "keymap", "loader", "text", + "tty", }; for (names) |n| { gen_step.addFileArg(b.path(b.fmt("runtime/lua/vim/{s}.lua", .{n}))); diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 47726537ac..856f92e738 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -353,6 +353,7 @@ set(LUA_INSPECT_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/inspect.lua) set(LUA_KEYMAP_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/keymap.lua) set(LUA_LOADER_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/loader.lua) set(LUA_TEXT_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/text.lua) +set(LUA_TTY_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/tty.lua) # Find all Lua modules in the _core/ directory. file(GLOB LUA_CORE_MODULE_SOURCES ${NVIM_RUNTIME_DIR}/lua/vim/_core/*.lua) @@ -652,6 +653,7 @@ add_custom_command( ${LUA_KEYMAP_MODULE_SOURCE} "vim.keymap" ${LUA_LOADER_MODULE_SOURCE} "vim.loader" ${LUA_TEXT_MODULE_SOURCE} "vim.text" + ${LUA_TTY_MODULE_SOURCE} "vim.tty" ${LUA_CORE_COMMAND_ARGS} DEPENDS ${CHAR_BLOB_GENERATOR} @@ -663,6 +665,7 @@ add_custom_command( ${LUA_KEYMAP_MODULE_SOURCE} ${LUA_LOADER_MODULE_SOURCE} ${LUA_TEXT_MODULE_SOURCE} + ${LUA_TTY_MODULE_SOURCE} ${LUA_CORE_MODULE_SOURCES} VERBATIM ) diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua index 313900938f..ee301f0675 100644 --- a/test/functional/core/main_spec.lua +++ b/test/functional/core/main_spec.lua @@ -247,6 +247,7 @@ describe('vim._core', function() 'vim.keymap', 'vim.loader', 'vim.text', + 'vim.tty', } if n.exec_lua [[return not not _G.jit]] then expected = vim.list_extend({