diff options
-rw-r--r-- | runtime/doc/if_lua.txt | 16 | ||||
-rw-r--r-- | src/nvim/lua/converter.c | 14 | ||||
-rw-r--r-- | src/nvim/lua/executor.c | 63 | ||||
-rw-r--r-- | src/nvim/lua/vim.lua | 12 | ||||
-rw-r--r-- | test/functional/lua/utility_functions_spec.lua | 28 |
5 files changed, 130 insertions, 3 deletions
diff --git a/runtime/doc/if_lua.txt b/runtime/doc/if_lua.txt index 8528085f47..d527a91a93 100644 --- a/runtime/doc/if_lua.txt +++ b/runtime/doc/if_lua.txt @@ -588,6 +588,22 @@ 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.fn.{func}({...}) + Call vimL function {func} with arguments. {func} can be both builtin + functions and user functions. To call autoload functions, use the + syntax `vim.fn['some#function']({...})` + + Note: unlike vim.api.|nvim_call_function| this converts values directly + between vimL values and lua values. If the vimL function returns a + float, it will be representeted directly as a lua number. Both empty + lists and dictonaries will be represented by an empty table. + + Note: vim.fn keys are generated on demand. So `pairs(vim.fn)` + does NOT work to enumerate all functions. + +vim.call({func}, {...}) + Call vim script function {func}. Equivalent to `vim.fn[func]({...})` + vim.type_idx *vim.type_idx* Type index for use in |lua-special-tbl|. Specifying one of the values from |vim.types| allows typing the empty table (it is diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 9665655e74..844232c64a 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -401,6 +401,8 @@ nlua_pop_typval_table_processing_end: return ret; } +static bool typval_conv_special = false; + #define TYPVAL_ENCODE_ALLOW_SPECIALS true #define TYPVAL_ENCODE_CONV_NIL(tv) \ @@ -439,7 +441,13 @@ nlua_pop_typval_table_processing_end: lua_createtable(lstate, 0, 0) #define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ - nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary) + do { \ + if (typval_conv_special) { \ + nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary); \ + } else { \ + lua_createtable(lstate, 0, 0); \ + } \ + } while (0) #define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ do { \ @@ -548,9 +556,11 @@ nlua_pop_typval_table_processing_end: /// @param[in] tv typval_T to convert. /// /// @return true in case of success, false otherwise. -bool nlua_push_typval(lua_State *lstate, typval_T *const tv) +bool nlua_push_typval(lua_State *lstate, typval_T *const tv, bool special) { + typval_conv_special = special; const int initial_size = lua_gettop(lstate); + if (!lua_checkstack(lstate, initial_size + 2)) { emsgf(_("E1502: Lua failed to grow stack to %i"), initial_size + 4); return false; diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 127458fe39..b7c1d57964 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -295,6 +295,9 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL // in_fast_event lua_pushcfunction(lstate, &nlua_in_fast_event); lua_setfield(lstate, -2, "in_fast_event"); + // call + lua_pushcfunction(lstate, &nlua_call); + lua_setfield(lstate, -2, "call"); // vim.loop luv_set_loop(lstate, &main_loop.uv); @@ -539,6 +542,64 @@ int nlua_in_fast_event(lua_State *lstate) return 1; } +int nlua_call(lua_State *lstate) +{ + Error err = ERROR_INIT; + size_t name_len; + const char_u *name = (const char_u *)luaL_checklstring(lstate, 1, &name_len); + int nargs = lua_gettop(lstate)-1; + if (nargs > MAX_FUNC_ARGS) { + return luaL_error(lstate, "Function called with too many arguments"); + } + + typval_T vim_args[MAX_FUNC_ARGS + 1]; + int i = 0; // also used for freeing the variables + for (; i < nargs; i++) { + lua_pushvalue(lstate, (int)i+2); + if (!nlua_pop_typval(lstate, &vim_args[i])) { + api_set_error(&err, kErrorTypeException, + "error converting argument %d", i+1); + goto free_vim_args; + } + } + + // TODO(bfredl): this should be simplified in error handling refactor + struct msglist **saved_msg_list = msg_list; + struct msglist *private_msg_list = NULL; + msg_list = &private_msg_list; + + force_abort = false; + suppress_errthrow = false; + current_exception = NULL; + did_emsg = false; + + try_start(); + typval_T rettv; + int dummy; + // call_func() retval is deceptive, ignore it. Instead we set `msg_list` + // (see above) to capture abort-causing non-exception errors. + (void)call_func(name, (int)name_len, &rettv, nargs, + vim_args, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &dummy, true, NULL, NULL); + if (!try_end(&err)) { + nlua_push_typval(lstate, &rettv, false); + } + tv_clear(&rettv); + + msg_list = saved_msg_list; + +free_vim_args: + while (i > 0) { + tv_clear(&vim_args[--i]); + } + if (ERROR_SET(&err)) { + lua_pushstring(lstate, err.msg); + api_clear_error(&err); + return lua_error(lstate); + } + return 1; +} + #ifdef WIN32 /// os.getenv: override os.getenv to maintain coherency. #9681 /// @@ -623,7 +684,7 @@ void executor_eval_lua(const String str, typval_T *const arg, if (arg->v_type == VAR_UNKNOWN) { lua_pushnil(lstate); } else { - nlua_push_typval(lstate, arg); + nlua_push_typval(lstate, arg, true); } if (lua_pcall(lstate, 1, 1, 0)) { nlua_error(lstate, diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index b67762e48e..5514819a02 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -242,6 +242,17 @@ local function __index(t, key) end end + +-- vim.fn.{func}(...) +local function fn_index(t, key) + local function func(...) + return vim.call(key, ...) + end + t[key] = func + return func +end +local fn = setmetatable({}, {__index=fn_index}) + local module = { _update_package_paths = _update_package_paths, _os_proc_children = _os_proc_children, @@ -249,6 +260,7 @@ local module = { _system = _system, paste = paste, schedule_wrap = schedule_wrap, + fn=fn, } setmetatable(module, { diff --git a/test/functional/lua/utility_functions_spec.lua b/test/functional/lua/utility_functions_spec.lua index a51334398c..0b789e84b0 100644 --- a/test/functional/lua/utility_functions_spec.lua +++ b/test/functional/lua/utility_functions_spec.lua @@ -10,6 +10,7 @@ local feed = helpers.feed local pcall_err = helpers.pcall_err local exec_lua = helpers.exec_lua local matches = helpers.matches +local source = helpers.source before_each(clear) @@ -299,4 +300,31 @@ describe('lua stdlib', function() eq("Error executing lua: .../shared.lua: Expected string, got number", pcall_err(exec_lua, [[return vim.pesc(2)]])) end) + + it('vim.call and vim.fn', function() + eq(true, exec_lua([[return vim.call('sin', 0.0) == 0.0 ]])) + eq(true, exec_lua([[return vim.fn.sin(0.0) == 0.0 ]])) + -- compat: nvim_call_function uses "special" value for vimL float + eq(false, exec_lua([[return vim.api.nvim_call_function('sin', {0.0}) == 0.0 ]])) + + source([[ + func! FooFunc(test) + let g:test = a:test + return {} + endfunc + func! VarArg(...) + return a:000 + endfunc + ]]) + eq(true, exec_lua([[return next(vim.fn.FooFunc(3)) == nil ]])) + eq(3, eval("g:test")) + -- compat: nvim_call_function uses "special" value for empty dict + eq(true, exec_lua([[return next(vim.api.nvim_call_function("FooFunc", {5})) == true ]])) + eq(5, eval("g:test")) + + eq({2, "foo", true}, exec_lua([[return vim.fn.VarArg(2, "foo", true)]])) + + -- error handling + eq({false, 'Vim:E714: List required'}, exec_lua([[return {pcall(vim.fn.add, "aa", "bb")}]])) + end) end) |