mirror of
https://github.com/neovim/neovim.git
synced 2026-05-24 05:40:08 +00:00
refactor(lua): move vim.wait into runtime Lua
Move vim.wait into runtime/lua/vim/_core/editor.lua and replace the C entrypoint with narrow vim._core helpers for polling, UI flushing, and interrupt checks. Keep the existing interval semantics by retaining the dummy timer that wakes the loop while it is otherwise idle. Update the docs to describe the success return values correctly, and adjust the test expectation for the new vim.validate() callback error. AI-assisted: Codex
This commit is contained in:
committed by
Lewis Russell
parent
4431713285
commit
c822a2657c
@@ -853,53 +853,6 @@ vim.ui_detach({ns}) *vim.ui_detach()*
|
||||
Parameters: ~
|
||||
• {ns} (`integer`) Namespace ID
|
||||
|
||||
vim.wait({time}, {callback}, {interval}, {fast_only}) *vim.wait()*
|
||||
Waits up to `time` milliseconds, until `callback` returns `true`
|
||||
(success). Executes `callback` immediately, then on user events, internal
|
||||
events, and approximately every `interval` milliseconds (default 200).
|
||||
Returns all `callback` results on success.
|
||||
|
||||
Nvim processes other events while waiting. Cannot be called during an
|
||||
|api-fast| event.
|
||||
|
||||
Examples: >lua
|
||||
-- Wait for 100 ms, allowing other events to process.
|
||||
vim.wait(100)
|
||||
|
||||
-- Wait up to 1000 ms or until `vim.g.foo` is true, at intervals of ~500 ms.
|
||||
vim.wait(1000, function() return vim.g.foo end, 500)
|
||||
|
||||
-- Wait indefinitely until `vim.g.foo` is true, and get the callback results.
|
||||
local ok, rv1, rv2, rv3 = vim.wait(math.huge, function()
|
||||
return vim.g.foo, 'a', 42, { ok = { 'yes' } }
|
||||
end)
|
||||
|
||||
-- Schedule a function to set a value in 100ms. This would wait 10s if blocked, but actually
|
||||
-- only waits 100ms because `vim.wait` processes other events while waiting.
|
||||
vim.defer_fn(function() vim.g.timer_result = true end, 100)
|
||||
if vim.wait(10000, function() return vim.g.timer_result end) then
|
||||
print('Only waiting a little bit of time!')
|
||||
end
|
||||
<
|
||||
|
||||
Parameters: ~
|
||||
• {time} (`number`) Number of milliseconds to wait. Must be
|
||||
non-negative number, any fractional part is truncated.
|
||||
• {callback} (`fun(): boolean, ...?`) Optional callback. Waits until
|
||||
{callback} returns true
|
||||
• {interval} (`integer?`) (Approximate) number of milliseconds to wait
|
||||
between polls
|
||||
• {fast_only} (`boolean?`) If true, only |api-fast| events will be
|
||||
processed.
|
||||
|
||||
Return (multiple): ~
|
||||
(`boolean`)
|
||||
(`-1|-2?`)
|
||||
• If callback returns `true` before timeout: `true, nil, ...`
|
||||
• On timeout: `false, -1`
|
||||
• On interrupt: `false, -2`
|
||||
• On error: the error is raised.
|
||||
|
||||
|
||||
==============================================================================
|
||||
LUA-VIMSCRIPT BRIDGE *lua-vimscript*
|
||||
@@ -1519,6 +1472,54 @@ vim.str_utfindex({s}, {encoding}, {index}, {strict_indexing})
|
||||
Return: ~
|
||||
(`integer`)
|
||||
|
||||
vim.wait({time}, {callback}, {interval}, {fast_only}) *vim.wait()*
|
||||
Waits up to `time` milliseconds, until `callback` returns `true`
|
||||
(success). Executes `callback` immediately, then on user events, internal
|
||||
events, and approximately every `interval` milliseconds (default 200).
|
||||
Returns `true` plus any remaining callback results on success.
|
||||
|
||||
Nvim processes other events while waiting. Cannot be called during an
|
||||
|api-fast| event.
|
||||
|
||||
Examples: >lua
|
||||
-- Wait for 100 ms, allowing other events to process.
|
||||
vim.wait(100)
|
||||
|
||||
-- Wait up to 1000 ms or until `vim.g.foo` is true, at intervals of ~500 ms.
|
||||
vim.wait(1000, function() return vim.g.foo end, 500)
|
||||
|
||||
-- Wait indefinitely until `vim.g.foo` is true, and get the callback results.
|
||||
local ok, rv1, rv2, rv3 = vim.wait(math.huge, function()
|
||||
return vim.g.foo, 'a', 42, { ok = { 'yes' } }
|
||||
end)
|
||||
|
||||
-- Schedule a function to set a value in 100ms. This would wait 10s if blocked, but actually
|
||||
-- only waits 100ms because `vim.wait` processes other events while waiting.
|
||||
vim.defer_fn(function() vim.g.timer_result = true end, 100)
|
||||
if vim.wait(10000, function() return vim.g.timer_result end) then
|
||||
print('Only waiting a little bit of time!')
|
||||
end
|
||||
<
|
||||
|
||||
Parameters: ~
|
||||
• {time} (`number`) Number of milliseconds to wait. Must be
|
||||
non-negative number, any fractional part is truncated.
|
||||
• {callback} (`fun(): boolean, ...?`) Optional callback. Waits until
|
||||
{callback} returns true
|
||||
• {interval} (`integer?`) (Approximate) number of milliseconds to wait
|
||||
between polls
|
||||
• {fast_only} (`boolean?`) If true, only |api-fast| events will be
|
||||
processed.
|
||||
|
||||
Return (multiple): ~
|
||||
(`boolean`)
|
||||
(`-1|-2?`)
|
||||
• If callback returns `true` before timeout: `true, ...` (remaining
|
||||
callback results).
|
||||
• On timeout: `false, -1`
|
||||
• On interrupt: `false, -2`
|
||||
• On error: the error is raised.
|
||||
|
||||
|
||||
==============================================================================
|
||||
Lua module: vim.inspector *vim.inspector*
|
||||
|
||||
@@ -40,6 +40,137 @@ vim._extra = {
|
||||
inspect_pos = true,
|
||||
}
|
||||
|
||||
--- Waits up to `time` milliseconds, until `callback` returns `true` (success). Executes
|
||||
--- `callback` immediately, then on user events, internal events, and approximately every
|
||||
--- `interval` milliseconds (default 200). Returns `true` plus any remaining callback
|
||||
--- results on success.
|
||||
---
|
||||
--- Nvim processes other events while waiting.
|
||||
--- Cannot be called during an |api-fast| event.
|
||||
---
|
||||
--- Examples:
|
||||
---
|
||||
--- ```lua
|
||||
--- -- Wait for 100 ms, allowing other events to process.
|
||||
--- vim.wait(100)
|
||||
---
|
||||
--- -- Wait up to 1000 ms or until `vim.g.foo` is true, at intervals of ~500 ms.
|
||||
--- vim.wait(1000, function() return vim.g.foo end, 500)
|
||||
---
|
||||
--- -- Wait indefinitely until `vim.g.foo` is true, and get the callback results.
|
||||
--- local ok, rv1, rv2, rv3 = vim.wait(math.huge, function()
|
||||
--- return vim.g.foo, 'a', 42, { ok = { 'yes' } }
|
||||
--- end)
|
||||
---
|
||||
--- -- Schedule a function to set a value in 100ms. This would wait 10s if blocked, but actually
|
||||
--- -- only waits 100ms because `vim.wait` processes other events while waiting.
|
||||
--- vim.defer_fn(function() vim.g.timer_result = true end, 100)
|
||||
--- if vim.wait(10000, function() return vim.g.timer_result end) then
|
||||
--- print('Only waiting a little bit of time!')
|
||||
--- end
|
||||
--- ```
|
||||
---
|
||||
--- @param time number Number of milliseconds to wait. Must be non-negative number, any fractional
|
||||
--- part is truncated.
|
||||
--- @param callback? fun(): boolean, ... Optional callback. Waits until {callback} returns true
|
||||
--- @param interval? integer (Approximate) number of milliseconds to wait between polls
|
||||
--- @param fast_only? boolean If true, only |api-fast| events will be processed.
|
||||
--- @return boolean, nil|-1|-2, ...
|
||||
--- - If callback returns `true` before timeout: `true, ...` (remaining callback results).
|
||||
--- - On timeout: `false, -1`
|
||||
--- - On interrupt: `false, -2`
|
||||
--- - On error: the error is raised.
|
||||
function vim.wait(time, callback, interval, fast_only)
|
||||
if vim.in_fast_event() then
|
||||
error('E5560: vim.wait must not be called in a fast event context', 0)
|
||||
end
|
||||
|
||||
vim.validate('time', time, 'number')
|
||||
if time < 0 then
|
||||
error('timeout must be >= 0')
|
||||
end
|
||||
local has_deadline = time == time and time ~= math.huge
|
||||
if has_deadline then
|
||||
time = math.floor(time)
|
||||
end
|
||||
|
||||
vim.validate('callback', callback, 'callable', true)
|
||||
|
||||
vim.validate('interval', interval, 'number', true)
|
||||
if interval then
|
||||
interval = math.floor(interval)
|
||||
if interval < 0 then
|
||||
error('interval must be >= 0')
|
||||
end
|
||||
else
|
||||
interval = 200
|
||||
end
|
||||
|
||||
vim.validate('fast_only', fast_only, 'boolean', true)
|
||||
if fast_only == nil then
|
||||
fast_only = false
|
||||
end
|
||||
|
||||
local start = vim.uv.hrtime()
|
||||
local dummy_timer --- @type uv.uv_timer_t?
|
||||
|
||||
local function cleanup()
|
||||
if dummy_timer and not dummy_timer:is_closing() then
|
||||
dummy_timer:stop()
|
||||
dummy_timer:close()
|
||||
end
|
||||
end
|
||||
|
||||
if interval > 0 then
|
||||
dummy_timer = assert(vim.uv.new_timer())
|
||||
dummy_timer:start(interval, interval, function()
|
||||
-- If Nvim exits while loop_poll() is blocked, vim.wait() does not
|
||||
-- resume.
|
||||
if vim.v.exiting ~= vim.NIL then
|
||||
cleanup()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Flush screen updates before blocking.
|
||||
vim._core.ui_flush()
|
||||
|
||||
while true do
|
||||
if vim._core.check_interrupt() then
|
||||
cleanup()
|
||||
return false, -2
|
||||
end
|
||||
|
||||
if callback then
|
||||
local results = vim.F.pack_len(pcall(callback))
|
||||
if not results[1] then
|
||||
cleanup()
|
||||
error(results[2], 0)
|
||||
elseif results[2] then
|
||||
cleanup()
|
||||
return true, unpack(results, 3, results.n)
|
||||
end
|
||||
end
|
||||
|
||||
local poll_timeout = -1
|
||||
if has_deadline then
|
||||
local remaining_ms = time - (vim.uv.hrtime() - start) / 1e6
|
||||
if remaining_ms <= 0 then
|
||||
cleanup()
|
||||
return false, -1
|
||||
end
|
||||
|
||||
-- loop_poll() takes an integer timeout, so cap each individual poll
|
||||
-- while still honoring larger overall waits.
|
||||
poll_timeout = math.min(math.ceil(remaining_ms), vim._maxint)
|
||||
end
|
||||
|
||||
-- The dummy timer wakes `vim._core.loop_poll()` on interval boundaries. Without it,
|
||||
-- polling would only resume for unrelated events or the final timeout.
|
||||
vim._core.loop_poll(poll_timeout, fast_only)
|
||||
end
|
||||
end
|
||||
|
||||
--- @nodoc
|
||||
vim.log = {
|
||||
--- @enum vim.log.levels
|
||||
|
||||
@@ -183,46 +183,24 @@ function vim.iconv(str, from, to, opts) end
|
||||
--- @return string? err Error message if scheduling failed, `nil` otherwise.
|
||||
function vim.schedule(fn) end
|
||||
|
||||
--- Waits up to `time` milliseconds, until `callback` returns `true` (success). Executes
|
||||
--- `callback` immediately, then on user events, internal events, and approximately every
|
||||
--- `interval` milliseconds (default 200). Returns all `callback` results on success.
|
||||
---
|
||||
--- Nvim processes other events while waiting.
|
||||
--- Cannot be called during an |api-fast| event.
|
||||
---
|
||||
--- Examples:
|
||||
---
|
||||
--- ```lua
|
||||
--- -- Wait for 100 ms, allowing other events to process.
|
||||
--- vim.wait(100)
|
||||
---
|
||||
--- -- Wait up to 1000 ms or until `vim.g.foo` is true, at intervals of ~500 ms.
|
||||
--- vim.wait(1000, function() return vim.g.foo end, 500)
|
||||
---
|
||||
--- -- Wait indefinitely until `vim.g.foo` is true, and get the callback results.
|
||||
--- local ok, rv1, rv2, rv3 = vim.wait(math.huge, function()
|
||||
--- return vim.g.foo, 'a', 42, { ok = { 'yes' } }
|
||||
--- end)
|
||||
---
|
||||
--- -- Schedule a function to set a value in 100ms. This would wait 10s if blocked, but actually
|
||||
--- -- only waits 100ms because `vim.wait` processes other events while waiting.
|
||||
--- vim.defer_fn(function() vim.g.timer_result = true end, 100)
|
||||
--- if vim.wait(10000, function() return vim.g.timer_result end) then
|
||||
--- print('Only waiting a little bit of time!')
|
||||
--- end
|
||||
--- ```
|
||||
---
|
||||
--- @param time number Number of milliseconds to wait. Must be non-negative number, any fractional
|
||||
--- part is truncated.
|
||||
--- @param callback? fun(): boolean, ... Optional callback. Waits until {callback} returns true
|
||||
--- @param interval? integer (Approximate) number of milliseconds to wait between polls
|
||||
--- @param fast_only? boolean If true, only |api-fast| events will be processed.
|
||||
--- @return boolean, nil|-1|-2, ...
|
||||
--- - If callback returns `true` before timeout: `true, nil, ...`
|
||||
--- - On timeout: `false, -1`
|
||||
--- - On interrupt: `false, -2`
|
||||
--- - On error: the error is raised.
|
||||
function vim.wait(time, callback, interval, fast_only) end
|
||||
---@nodoc
|
||||
---@class vim._core
|
||||
vim._core = {}
|
||||
|
||||
--- @nodoc
|
||||
--- Polls the main event loop for up to {timeout} milliseconds.
|
||||
--- @param timeout integer
|
||||
--- @param fast_only boolean
|
||||
function vim._core.loop_poll(timeout, fast_only) end
|
||||
|
||||
--- @nodoc
|
||||
--- Flushes pending UI updates.
|
||||
function vim._core.ui_flush() end
|
||||
|
||||
--- @nodoc
|
||||
--- Checks for interrupt, clears it, and consumes input if present.
|
||||
--- @return boolean
|
||||
function vim._core.check_interrupt() end
|
||||
|
||||
--- Subscribe to |ui-events|, similar to |nvim_ui_attach()| but receive events in a Lua callback.
|
||||
--- Used to implement screen elements like popupmenu or message handling in Lua.
|
||||
|
||||
@@ -520,148 +520,36 @@ static int nlua_schedule(lua_State *const lstate)
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Dummy timer callback. Used by vim.wait().
|
||||
static void dummy_timer_due_cb(TimeWatcher *tw, void *data)
|
||||
{
|
||||
// If the main loop is closing, the condition won't be checked again.
|
||||
// Close the timer to avoid leaking resources.
|
||||
if (main_loop.closing) {
|
||||
time_watcher_stop(tw);
|
||||
time_watcher_close(tw, dummy_timer_close_cb);
|
||||
}
|
||||
}
|
||||
|
||||
// Dummy timer close callback. Used by vim.wait().
|
||||
static void dummy_timer_close_cb(TimeWatcher *tw, void *data)
|
||||
{
|
||||
xfree(tw);
|
||||
}
|
||||
|
||||
static bool nlua_wait_condition(lua_State *lstate, int *status, bool *callback_result,
|
||||
int *nresults)
|
||||
{
|
||||
int top = lua_gettop(lstate);
|
||||
lua_pushvalue(lstate, 2);
|
||||
*status = nlua_pcall(lstate, 0, LUA_MULTRET);
|
||||
if (*status) {
|
||||
return true; // break on error, but keep error on stack
|
||||
}
|
||||
*nresults = lua_gettop(lstate) - top;
|
||||
if (*nresults == 0) {
|
||||
*callback_result = false;
|
||||
return false;
|
||||
}
|
||||
*callback_result = lua_toboolean(lstate, top + 1);
|
||||
if (!*callback_result) {
|
||||
lua_settop(lstate, top);
|
||||
return false;
|
||||
}
|
||||
lua_remove(lstate, top + 1);
|
||||
(*nresults)--;
|
||||
return true; // break if true
|
||||
}
|
||||
|
||||
/// "vim.wait(timeout, condition[, interval])" function
|
||||
static int nlua_wait(lua_State *lstate)
|
||||
static int nlua_loop_poll(lua_State *lstate)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
if (in_fast_callback) {
|
||||
return luaL_error(lstate, e_fast_api_disabled, "vim.wait");
|
||||
}
|
||||
|
||||
double timeout_number = luaL_checknumber(lstate, 1);
|
||||
if (timeout_number < 0) {
|
||||
return luaL_error(lstate, "timeout must be >= 0");
|
||||
}
|
||||
int64_t timeout = (isnan(timeout_number) || timeout_number > (double)INT64_MAX)
|
||||
? INT64_MAX
|
||||
: (int64_t)timeout_number;
|
||||
|
||||
int lua_top = lua_gettop(lstate);
|
||||
|
||||
// Check if condition can be called.
|
||||
bool is_function = false;
|
||||
if (lua_top >= 2 && !lua_isnil(lstate, 2)) {
|
||||
is_function = (lua_type(lstate, 2) == LUA_TFUNCTION);
|
||||
|
||||
// Check if condition is callable table
|
||||
if (!is_function && luaL_getmetafield(lstate, 2, "__call") != 0) {
|
||||
is_function = (lua_type(lstate, -1) == LUA_TFUNCTION);
|
||||
lua_pop(lstate, 1);
|
||||
}
|
||||
|
||||
if (!is_function) {
|
||||
lua_pushliteral(lstate, "vim.wait: callback must be callable");
|
||||
return lua_error(lstate);
|
||||
}
|
||||
}
|
||||
|
||||
intptr_t interval = 200;
|
||||
if (lua_top >= 3 && !lua_isnil(lstate, 3)) {
|
||||
interval = luaL_checkinteger(lstate, 3);
|
||||
if (interval < 0) {
|
||||
return luaL_error(lstate, "interval must be >= 0");
|
||||
}
|
||||
}
|
||||
|
||||
bool fast_only = false;
|
||||
if (lua_top >= 4) {
|
||||
fast_only = lua_toboolean(lstate, 4);
|
||||
}
|
||||
int64_t timeout = (int64_t)luaL_checkinteger(lstate, 1);
|
||||
bool fast_only = lua_toboolean(lstate, 2);
|
||||
|
||||
MultiQueue *loop_events = fast_only ? main_loop.fast_events : main_loop.events;
|
||||
LOOP_PROCESS_EVENTS(&main_loop, loop_events, timeout);
|
||||
|
||||
TimeWatcher *tw = xmalloc(sizeof(TimeWatcher));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Start dummy timer.
|
||||
time_watcher_init(&main_loop, tw, NULL);
|
||||
// Don't schedule the due callback, as that'll lead to two different types of events
|
||||
// on each interval, causing the condition to be checked twice.
|
||||
tw->events = NULL;
|
||||
time_watcher_start(tw, dummy_timer_due_cb, (uint64_t)interval, (uint64_t)interval);
|
||||
|
||||
int pcall_status = 0;
|
||||
bool callback_result = false;
|
||||
int nresults = 0;
|
||||
|
||||
// Flush screen updates before blocking.
|
||||
static int nlua_ui_flush(lua_State *lstate)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
ui_flush();
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOOP_PROCESS_EVENTS_UNTIL(&main_loop,
|
||||
loop_events,
|
||||
timeout,
|
||||
got_int || (is_function ? nlua_wait_condition(lstate,
|
||||
&pcall_status,
|
||||
&callback_result,
|
||||
&nresults)
|
||||
: false));
|
||||
|
||||
// Stop dummy timer
|
||||
time_watcher_stop(tw);
|
||||
time_watcher_close(tw, dummy_timer_close_cb);
|
||||
|
||||
if (pcall_status) {
|
||||
return lua_error(lstate);
|
||||
} else if (callback_result) {
|
||||
lua_pushboolean(lstate, 1);
|
||||
if (nresults == 0) {
|
||||
lua_pushnil(lstate);
|
||||
nresults = 1;
|
||||
} else {
|
||||
lua_insert(lstate, -1 - nresults);
|
||||
}
|
||||
return nresults + 1;
|
||||
} else if (got_int) {
|
||||
static int nlua_check_interrupt(lua_State *lstate)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
bool interrupted = got_int;
|
||||
if (got_int) {
|
||||
got_int = false;
|
||||
vgetc();
|
||||
lua_pushboolean(lstate, 0);
|
||||
lua_pushinteger(lstate, -2);
|
||||
return 2;
|
||||
} else {
|
||||
lua_pushboolean(lstate, 0);
|
||||
lua_pushinteger(lstate, -1);
|
||||
return 2;
|
||||
}
|
||||
|
||||
lua_pushboolean(lstate, interrupted);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static nlua_ref_state_t *nlua_new_ref_state(lua_State *lstate, bool is_thread)
|
||||
@@ -718,6 +606,10 @@ static void nlua_common_vim_init(lua_State *lstate, bool is_thread, bool is_stan
|
||||
lua_pushcfunction(lstate, &nlua_is_thread);
|
||||
lua_setfield(lstate, -2, "is_thread");
|
||||
|
||||
// vim._core
|
||||
lua_createtable(lstate, 0, 0);
|
||||
lua_setfield(lstate, -2, "_core");
|
||||
|
||||
// vim.NIL
|
||||
lua_newuserdata(lstate, 0);
|
||||
lua_createtable(lstate, 0, 0);
|
||||
@@ -929,10 +821,6 @@ static bool nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
|
||||
lua_pushcfunction(lstate, &nlua_rpcnotify);
|
||||
lua_setfield(lstate, -2, "rpcnotify");
|
||||
|
||||
// wait
|
||||
lua_pushcfunction(lstate, &nlua_wait);
|
||||
lua_setfield(lstate, -2, "wait");
|
||||
|
||||
// ui_attach
|
||||
lua_pushcfunction(lstate, &nlua_ui_attach);
|
||||
lua_setfield(lstate, -2, "ui_attach");
|
||||
@@ -943,6 +831,16 @@ static bool nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
|
||||
|
||||
nlua_common_vim_init(lstate, false, false);
|
||||
|
||||
// vim._core wait helpers
|
||||
lua_getfield(lstate, -1, "_core");
|
||||
lua_pushcfunction(lstate, &nlua_loop_poll);
|
||||
lua_setfield(lstate, -2, "loop_poll");
|
||||
lua_pushcfunction(lstate, &nlua_ui_flush);
|
||||
lua_setfield(lstate, -2, "ui_flush");
|
||||
lua_pushcfunction(lstate, &nlua_check_interrupt);
|
||||
lua_setfield(lstate, -2, "check_interrupt");
|
||||
lua_pop(lstate, 1);
|
||||
|
||||
// patch require() (only for --startuptime)
|
||||
if (time_fd != NULL) {
|
||||
lua_getglobal(lstate, "require");
|
||||
|
||||
@@ -2371,10 +2371,9 @@ describe('lua stdlib', function()
|
||||
end)
|
||||
|
||||
it('callback must be a function', function()
|
||||
eq(
|
||||
{ false, 'vim.wait: callback must be callable' },
|
||||
exec_lua [[return {pcall(function() vim.wait(1000, 13) end)}]]
|
||||
)
|
||||
local result = exec_lua [[return {pcall(function() vim.wait(1000, 13) end)}]]
|
||||
eq(false, result[1])
|
||||
matches('callback: expected callable, got number$', remove_trace(result[2]))
|
||||
end)
|
||||
|
||||
it('waits if callback arg is nil', function()
|
||||
|
||||
Reference in New Issue
Block a user