mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
lua: vim.wait implementation
This commit is contained in:
parent
504d6878da
commit
be662fe5c7
@ -827,6 +827,62 @@ vim.schedule({callback}) *vim.schedule()*
|
||||
Schedules {callback} to be invoked soon by the main event-loop. Useful
|
||||
to avoid |textlock| or other temporary restrictions.
|
||||
|
||||
|
||||
vim.defer_fn({fn}, {timeout}) *vim.defer_fn*
|
||||
Defers calling {fn} until {timeout} ms passes. Use to do a one-shot timer
|
||||
that calls {fn}.
|
||||
|
||||
Parameters: ~
|
||||
{fn} Callback to call once {timeout} expires
|
||||
{timeout} Time in ms to wait before calling {fn}
|
||||
|
||||
Returns: ~
|
||||
|vim.loop|.new_timer() object
|
||||
|
||||
vim.wait({time}, {callback} [, {interval}]) *vim.wait()*
|
||||
Wait for {time} in milliseconds until {callback} returns `true`.
|
||||
|
||||
Executes {callback} immediately and at approximately {interval}
|
||||
milliseconds (default 200). Nvim still processes other events during
|
||||
this time.
|
||||
|
||||
|
||||
Returns: ~
|
||||
If {callback} returns `true` during the {time}:
|
||||
`true, nil`
|
||||
|
||||
If {callback} never returns `true` during the {time}:
|
||||
`false, -1`
|
||||
|
||||
If {callback} is interrupted during the {time}:
|
||||
`false, -2`
|
||||
|
||||
If {callback} errors, the error is raised.
|
||||
|
||||
Examples: >
|
||||
|
||||
---
|
||||
-- Wait for 100 ms, allowing other events to process
|
||||
vim.wait(100, function() end)
|
||||
|
||||
---
|
||||
-- Wait for 100 ms or until global variable set.
|
||||
vim.wait(100, function() return vim.g.waiting_for_var end)
|
||||
|
||||
---
|
||||
-- Wait for 1 second or until global variable set, checking every ~500 ms
|
||||
vim.wait(1000, function() return vim.g.waiting_for_var end, 500)
|
||||
|
||||
---
|
||||
-- Schedule a function to set a value in 100ms
|
||||
vim.defer_fn(function() vim.g.timer_result = true end, 100)
|
||||
|
||||
-- Would wait ten seconds if results blocked. Actually only waits 100 ms
|
||||
if vim.wait(10000, function() return vim.g.timer_result end) then
|
||||
print('Only waiting a little bit of time!')
|
||||
end
|
||||
<
|
||||
|
||||
vim.fn.{func}({...}) *vim.fn*
|
||||
Invokes |vim-function| or |user-function| {func} with arguments {...}.
|
||||
To call autoload functions, use the syntax: >
|
||||
|
@ -46,31 +46,6 @@ local function is_dir(filename)
|
||||
return stat and stat.type == 'directory' or false
|
||||
end
|
||||
|
||||
-- TODO Use vim.wait when that is available, but provide an alternative for now.
|
||||
local wait = vim.wait or function(timeout_ms, condition, interval)
|
||||
validate {
|
||||
timeout_ms = { timeout_ms, 'n' };
|
||||
condition = { condition, 'f' };
|
||||
interval = { interval, 'n', true };
|
||||
}
|
||||
assert(timeout_ms > 0, "timeout_ms must be > 0")
|
||||
local _ = log.debug() and log.debug("wait.fallback", timeout_ms)
|
||||
interval = interval or 200
|
||||
local interval_cmd = "sleep "..interval.."m"
|
||||
local timeout = timeout_ms + uv.now()
|
||||
-- TODO is there a better way to sync this?
|
||||
while true do
|
||||
uv.update_time()
|
||||
if condition() then
|
||||
return 0
|
||||
end
|
||||
if uv.now() >= timeout then
|
||||
return -1
|
||||
end
|
||||
nvim_command(interval_cmd)
|
||||
-- vim.loop.sleep(10)
|
||||
end
|
||||
end
|
||||
local wait_result_reason = { [-1] = "timeout"; [-2] = "interrupted"; [-3] = "error" }
|
||||
|
||||
local valid_encodings = {
|
||||
@ -810,8 +785,8 @@ function lsp._vim_exit_handler()
|
||||
for _, client in pairs(active_clients) do
|
||||
client.stop()
|
||||
end
|
||||
local wait_result = wait(500, function() return tbl_isempty(active_clients) end, 50)
|
||||
if wait_result ~= 0 then
|
||||
|
||||
if not vim.wait(500, function() return tbl_isempty(active_clients) end, 50) then
|
||||
for _, client in pairs(active_clients) do
|
||||
client.stop(true)
|
||||
end
|
||||
@ -889,12 +864,14 @@ function lsp.buf_request_sync(bufnr, method, params, timeout_ms)
|
||||
for _ in pairs(client_request_ids) do
|
||||
expected_result_count = expected_result_count + 1
|
||||
end
|
||||
local wait_result = wait(timeout_ms or 100, function()
|
||||
|
||||
local wait_result, reason = vim.wait(timeout_ms or 100, function()
|
||||
return result_count >= expected_result_count
|
||||
end, 10)
|
||||
if wait_result ~= 0 then
|
||||
|
||||
if not wait_result then
|
||||
cancel()
|
||||
return nil, wait_result_reason[wait_result]
|
||||
return nil, wait_result_reason[reason]
|
||||
end
|
||||
return request_results
|
||||
end
|
||||
|
@ -289,7 +289,17 @@ static int nlua_wait(lua_State *lstate)
|
||||
if (timeout < 0) {
|
||||
return luaL_error(lstate, "timeout must be > 0");
|
||||
}
|
||||
if (lua_type(lstate, 2) != LUA_TFUNCTION) {
|
||||
|
||||
// Check if condition can be called.
|
||||
bool 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: condition must be a function");
|
||||
return lua_error(lstate);
|
||||
}
|
||||
@ -314,23 +324,11 @@ static int nlua_wait(lua_State *lstate)
|
||||
int pcall_status = 0;
|
||||
bool callback_result = false;
|
||||
|
||||
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, (int)timeout,
|
||||
nlua_wait_condition(lstate, &pcall_status,
|
||||
&callback_result)
|
||||
|| got_int);
|
||||
|
||||
if (pcall_status) {
|
||||
// TODO: add prefix to error?
|
||||
// handled after stopped time_watcher
|
||||
} else if (got_int) {
|
||||
got_int = false;
|
||||
vgetc();
|
||||
lua_pushinteger(lstate, -2);
|
||||
} else if (callback_result) {
|
||||
lua_pushinteger(lstate, 0);
|
||||
} else {
|
||||
lua_pushinteger(lstate, -1);
|
||||
}
|
||||
LOOP_PROCESS_EVENTS_UNTIL(
|
||||
&main_loop,
|
||||
main_loop.events,
|
||||
(int)timeout,
|
||||
nlua_wait_condition(lstate, &pcall_status, &callback_result) || got_int);
|
||||
|
||||
// Stop dummy timer
|
||||
time_watcher_stop(tw);
|
||||
@ -338,8 +336,20 @@ static int nlua_wait(lua_State *lstate)
|
||||
|
||||
if (pcall_status) {
|
||||
return lua_error(lstate);
|
||||
} else if (callback_result) {
|
||||
lua_pushboolean(lstate, 1);
|
||||
lua_pushnil(lstate);
|
||||
} else if (got_int) {
|
||||
got_int = false;
|
||||
vgetc();
|
||||
lua_pushboolean(lstate, 0);
|
||||
lua_pushinteger(lstate, -2);
|
||||
} else {
|
||||
lua_pushboolean(lstate, 0);
|
||||
lua_pushinteger(lstate, -1);
|
||||
}
|
||||
return 1;
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
/// Initialize lua interpreter state
|
||||
|
@ -1048,13 +1048,13 @@ describe('lua stdlib', function()
|
||||
end)
|
||||
|
||||
it('vim.defer_fn', function()
|
||||
exec_lua [[
|
||||
vim.g.test = 0
|
||||
vim.defer_fn(function() vim.g.test = 1 end, 50)
|
||||
]]
|
||||
eq(0, exec_lua[[return vim.g.test]])
|
||||
exec_lua [[vim.cmd("sleep 1000m")]]
|
||||
eq(1, exec_lua[[return vim.g.test]])
|
||||
eq(false, exec_lua [[
|
||||
vim.g.test = false
|
||||
vim.defer_fn(function() vim.g.test = true end, 150)
|
||||
return vim.g.test
|
||||
]])
|
||||
exec_lua [[vim.wait(1000, function() return vim.g.test end)]]
|
||||
eq(true, exec_lua[[return vim.g.test]])
|
||||
end)
|
||||
|
||||
it('vim.region', function()
|
||||
@ -1066,4 +1066,176 @@ describe('lua stdlib', function()
|
||||
eq({5,15}, exec_lua[[ return vim.region(0,{1,5},{1,14},'v',true)[1] ]])
|
||||
end)
|
||||
|
||||
describe('vim.wait', function()
|
||||
before_each(function()
|
||||
exec_lua[[
|
||||
-- high precision timer
|
||||
get_time = function()
|
||||
return vim.fn.reltimefloat(vim.fn.reltime())
|
||||
end
|
||||
]]
|
||||
end)
|
||||
|
||||
it('should run from lua', function()
|
||||
exec_lua[[vim.wait(100, function() return true end)]]
|
||||
end)
|
||||
|
||||
it('should wait the expected time if false', function()
|
||||
eq({time = true, wait_result = {false, -1}}, exec_lua[[
|
||||
start_time = get_time()
|
||||
wait_succeed, wait_fail_val = vim.wait(200, function() return false end)
|
||||
|
||||
return {
|
||||
-- 150ms waiting or more results in true. Flaky tests are bad.
|
||||
time = (start_time + 0.15) < get_time(),
|
||||
wait_result = {wait_succeed, wait_fail_val}
|
||||
}
|
||||
]])
|
||||
end)
|
||||
|
||||
|
||||
it('should not block other events', function()
|
||||
eq({time = true, wait_result = true}, exec_lua[[
|
||||
start_time = get_time()
|
||||
|
||||
vim.g.timer_result = false
|
||||
timer = vim.loop.new_timer()
|
||||
timer:start(100, 0, vim.schedule_wrap(function()
|
||||
vim.g.timer_result = true
|
||||
end))
|
||||
|
||||
-- Would wait ten seconds if results blocked.
|
||||
wait_result = vim.wait(10000, function() return vim.g.timer_result end)
|
||||
|
||||
return {
|
||||
time = (start_time + 5) > get_time(),
|
||||
wait_result = wait_result,
|
||||
}
|
||||
]])
|
||||
end)
|
||||
|
||||
it('should work with vim.defer_fn', function()
|
||||
eq({time = true, wait_result = true}, exec_lua[[
|
||||
start_time = get_time()
|
||||
|
||||
vim.defer_fn(function() vim.g.timer_result = true end, 100)
|
||||
wait_result = vim.wait(10000, function() return vim.g.timer_result end)
|
||||
|
||||
return {
|
||||
time = (start_time + 5) > get_time(),
|
||||
wait_result = wait_result,
|
||||
}
|
||||
]])
|
||||
end)
|
||||
|
||||
it('should require functions to be passed', function()
|
||||
local pcall_result = exec_lua [[
|
||||
return {pcall(function() vim.wait(1000, 13) end)}
|
||||
]]
|
||||
|
||||
eq(pcall_result[1], false)
|
||||
matches('condition must be a function', pcall_result[2])
|
||||
end)
|
||||
|
||||
it('should not crash when callback errors', function()
|
||||
local pcall_result = exec_lua [[
|
||||
return {pcall(function() vim.wait(1000, function() error("As Expected") end) end)}
|
||||
]]
|
||||
|
||||
eq(pcall_result[1], false)
|
||||
matches('As Expected', pcall_result[2])
|
||||
end)
|
||||
|
||||
it('should call callbacks exactly once if they return true immediately', function()
|
||||
eq(true, exec_lua [[
|
||||
vim.g.wait_count = 0
|
||||
vim.wait(1000, function()
|
||||
vim.g.wait_count = vim.g.wait_count + 1
|
||||
return true
|
||||
end, 20)
|
||||
return vim.g.wait_count == 1
|
||||
]])
|
||||
end)
|
||||
|
||||
it('should call callbacks few times with large `interval`', function()
|
||||
eq(true, exec_lua [[
|
||||
vim.g.wait_count = 0
|
||||
vim.wait(50, function() vim.g.wait_count = vim.g.wait_count + 1 end, 200)
|
||||
return vim.g.wait_count < 5
|
||||
]])
|
||||
end)
|
||||
|
||||
it('should call callbacks more times with small `interval`', function()
|
||||
eq(true, exec_lua [[
|
||||
vim.g.wait_count = 0
|
||||
vim.wait(50, function() vim.g.wait_count = vim.g.wait_count + 1 end, 5)
|
||||
return vim.g.wait_count > 5
|
||||
]])
|
||||
end)
|
||||
|
||||
it('should play nice with `not` when fails', function()
|
||||
eq(true, exec_lua [[
|
||||
if not vim.wait(50, function() end) then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
]])
|
||||
end)
|
||||
|
||||
it('should play nice with `if` when success', function()
|
||||
eq(true, exec_lua [[
|
||||
if vim.wait(50, function() return true end) then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
]])
|
||||
end)
|
||||
|
||||
it('should return immediately with false if timeout is 0', function()
|
||||
eq({false, -1}, exec_lua [[
|
||||
return {
|
||||
vim.wait(0, function() return false end)
|
||||
}
|
||||
]])
|
||||
end)
|
||||
|
||||
it('should work with tables with __call', function()
|
||||
eq(true, exec_lua [[
|
||||
local t = setmetatable({}, {__call = function(...) return true end})
|
||||
return vim.wait(100, t, 10)
|
||||
]])
|
||||
end)
|
||||
|
||||
it('should work with tables with __call that change', function()
|
||||
eq(true, exec_lua [[
|
||||
local t = {count = 0}
|
||||
setmetatable(t, {
|
||||
__call = function()
|
||||
t.count = t.count + 1
|
||||
return t.count > 3
|
||||
end
|
||||
})
|
||||
|
||||
return vim.wait(1000, t, 10)
|
||||
]])
|
||||
end)
|
||||
|
||||
it('should not work with negative intervals', function()
|
||||
local pcall_result = exec_lua [[
|
||||
return pcall(function() vim.wait(1000, function() return false end, -1) end)
|
||||
]]
|
||||
|
||||
eq(false, pcall_result)
|
||||
end)
|
||||
|
||||
it('should not work with weird intervals', function()
|
||||
local pcall_result = exec_lua [[
|
||||
return pcall(function() vim.wait(1000, function() return false end, 'a string value') end)
|
||||
]]
|
||||
|
||||
eq(false, pcall_result)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
Loading…
Reference in New Issue
Block a user