aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBjörn Linse <bjorn.linse@gmail.com>2019-06-05 11:49:39 +0200
committerGitHub <noreply@github.com>2019-06-05 11:49:39 +0200
commit3305769eae9f556ee731381d166e6955bcbec340 (patch)
treeefe13a5e0a42c0ca7c3589e761925960e15958ed
parent16ee24082f72162d3bdfbddb0b40b5abc2c90fda (diff)
parentb684bd05b513b57b4d67ea2f95f7713c0b18daab (diff)
downloadrneovim-3305769eae9f556ee731381d166e6955bcbec340.tar.gz
rneovim-3305769eae9f556ee731381d166e6955bcbec340.tar.bz2
rneovim-3305769eae9f556ee731381d166e6955bcbec340.zip
Merge pull request #10120 from bfredl/lua_schedule
Add lua function vim.schedule(cb) to defer callbacks to main loop
-rw-r--r--runtime/doc/api.txt2
-rw-r--r--runtime/doc/if_lua.txt6
-rw-r--r--src/nvim/lua/executor.c32
-rw-r--r--test/functional/lua/utility_functions_spec.lua50
4 files changed, 76 insertions, 14 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index e127ccae0c..a529a9b32e 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -203,6 +203,8 @@ User reloads the buffer with ":edit", emits: >
In-process lua plugins can also recieve buffer updates, in the form of lua
callbacks. These callbacks are called frequently in various contexts, buffer
contents or window layout should not be changed inside these |textlock|.
+|lua-vim.schedule| can be used to defer these operations to the main loop,
+where they are allowed.
|nvim_buf_attach| will take keyword args for the callbacks. "on_lines" will
receive parameters ("lines", {buf}, {changedtick}, {firstline}, {lastline}, {new_lastline}).
diff --git a/runtime/doc/if_lua.txt b/runtime/doc/if_lua.txt
index 92c25ba875..8ee5718349 100644
--- a/runtime/doc/if_lua.txt
+++ b/runtime/doc/if_lua.txt
@@ -379,6 +379,12 @@ vim.stricmp(a, b) *lua-vim.stricmp*
string arguments and returns 0, 1 or -1 if strings are equal, a is
greater then b or a is lesser then b respectively.
+vim.schedule(callback) *lua-vim.schedule*
+ Schedule `callback` to be called soon by the main event loop. This is
+ useful in contexts where some functionality is blocked, like an
+ autocommand or callback running with |textlock|. Then the scheduled
+ callback could invoke this functionality later when it is allowed.
+
vim.type_idx *lua-vim.type_idx*
Type index for use in |lua-special-tbl|. Specifying one of the
values from |lua-vim.types| allows typing the empty table (it is
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index fa8a67ff39..df08a9dd87 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -108,6 +108,35 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
return 1;
}
+static void nlua_schedule_event(void **argv)
+{
+ LuaRef cb = (LuaRef)(ptrdiff_t)argv[0];
+ lua_State *const lstate = nlua_enter();
+ nlua_pushref(lstate, cb);
+ nlua_unref(lstate, cb);
+ if (lua_pcall(lstate, 0, 0, 0)) {
+ nlua_error(lstate, _("Error executing vim.schedule lua callback: %.*s"));
+ }
+}
+
+/// Schedule Lua callback on main loop's event queue
+///
+/// @param lstate Lua interpreter state.
+static int nlua_schedule(lua_State *const lstate)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (lua_type(lstate, 1) != LUA_TFUNCTION) {
+ lua_pushliteral(lstate, "vim.schedule: expected function");
+ return lua_error(lstate);
+ }
+
+ LuaRef cb = nlua_ref(lstate, 1);
+
+ multiqueue_put(main_loop.events, nlua_schedule_event,
+ 1, (void *)(ptrdiff_t)cb);
+ return 0;
+}
+
/// Initialize lua interpreter state
///
/// Called by lua interpreter itself to initialize state.
@@ -143,6 +172,9 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
// stricmp
lua_pushcfunction(lstate, &nlua_stricmp);
lua_setfield(lstate, -2, "stricmp");
+ // schedule
+ lua_pushcfunction(lstate, &nlua_schedule);
+ lua_setfield(lstate, -2, "schedule");
lua_setglobal(lstate, "vim");
return 0;
diff --git a/test/functional/lua/utility_functions_spec.lua b/test/functional/lua/utility_functions_spec.lua
index 67f5ce24f7..780d3a1565 100644
--- a/test/functional/lua/utility_functions_spec.lua
+++ b/test/functional/lua/utility_functions_spec.lua
@@ -5,10 +5,13 @@ local funcs = helpers.funcs
local meths = helpers.meths
local clear = helpers.clear
local eq = helpers.eq
+local eval = helpers.eval
+local feed = helpers.feed
+local meth_pcall = helpers.meth_pcall
before_each(clear)
-describe('vim.stricmp', function()
+describe('lua function', function()
-- İ: `tolower("İ")` is `i` which has length 1 while `İ` itself has
-- length 2 (in bytes).
-- Ⱥ: `tolower("Ⱥ")` is `ⱥ` which has length 2 while `Ⱥ` itself has
@@ -17,7 +20,7 @@ describe('vim.stricmp', function()
-- Note: 'i' !=? 'İ' and 'ⱥ' !=? 'Ⱥ' on some systems.
-- Note: Built-in Nvim comparison (on systems lacking `strcasecmp`) works
-- only on ASCII characters.
- it('works', function()
+ it('vim.stricmp', function()
eq(0, funcs.luaeval('vim.stricmp("a", "A")'))
eq(0, funcs.luaeval('vim.stricmp("A", "a")'))
eq(0, funcs.luaeval('vim.stricmp("a", "a")'))
@@ -106,10 +109,35 @@ describe('vim.stricmp', function()
eq(1, funcs.luaeval('vim.stricmp("\\0c\\0", "\\0b\\0")'))
eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0B\\0")'))
end)
-end)
-describe("vim.split", function()
- it("works", function()
+ it("vim.schedule", function()
+ meths.execute_lua([[
+ test_table = {}
+ vim.schedule(function()
+ table.insert(test_table, "xx")
+ end)
+ table.insert(test_table, "yy")
+ ]], {})
+ eq({"yy","xx"}, meths.execute_lua("return test_table", {}))
+
+ -- type checked args
+ eq({false, 'Error executing lua: vim.schedule: expected function'},
+ meth_pcall(meths.execute_lua, "vim.schedule('stringly')", {}))
+
+ eq({false, 'Error executing lua: vim.schedule: expected function'},
+ meth_pcall(meths.execute_lua, "vim.schedule()", {}))
+
+ meths.execute_lua([[
+ vim.schedule(function()
+ error("big failure\nvery async")
+ end)
+ ]], {})
+
+ feed("<cr>")
+ eq('Error executing vim.schedule lua callback: [string "<nvim>"]:2: big failure\nvery async', eval("v:errmsg"))
+ end)
+
+ it("vim.split", function()
local split = function(str, sep)
return meths.execute_lua('return vim.split(...)', {str, sep})
end
@@ -141,10 +169,8 @@ describe("vim.split", function()
assert(string.match(err, "Infinite loop detected"))
end
end)
-end)
-describe("vim.trim", function()
- it('works', function()
+ it('vim.trim', function()
local trim = function(s)
return meths.execute_lua('return vim.trim(...)', { s })
end
@@ -164,10 +190,8 @@ describe("vim.trim", function()
eq(false, status)
assert(string.match(err, "Only strings can be trimmed"))
end)
-end)
-describe("vim.inspect", function()
- it('works', function()
+ it('vim.inspect', function()
-- just make sure it basically works, it has its own test suite
local inspect = function(t, opts)
return meths.execute_lua('return vim.inspect(...)', { t, opts })
@@ -187,10 +211,8 @@ describe("vim.inspect", function()
end})
]], {}))
end)
-end)
-describe("vim.deepcopy", function()
- it("works", function()
+ it("vim.deepcopy", function()
local is_dc = meths.execute_lua([[
local a = { x = { 1, 2 }, y = 5}
local b = vim.deepcopy(a)