aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTJ DeVries <devries.timothyj@gmail.com>2020-06-30 02:05:06 -0400
committerTJ DeVries <devries.timothyj@gmail.com>2020-07-10 20:23:12 -0400
commit6360cf7ce87407bd8a519b9a17f45b2148291904 (patch)
tree6f770f564307cb8a31f6ead427114267fcba3953
parent971a191c4d772493535d55524b994fe385fae546 (diff)
downloadrneovim-6360cf7ce87407bd8a519b9a17f45b2148291904.tar.gz
rneovim-6360cf7ce87407bd8a519b9a17f45b2148291904.tar.bz2
rneovim-6360cf7ce87407bd8a519b9a17f45b2148291904.zip
lua: Add ability to pass tables with __call
vim-patch:8.2.1054: not so easy to pass a lua function to Vim vim-patch:8.2.1084: Lua: registering function has useless code I think I have also opened up the possibility for people to use these callbacks elsewhere, since I've added a new struct that we should be able to use. Also, this should allow us to determine what the state of a list is in Lua or a dictionary in Lua, since we now can track the luaref as we go.
-rw-r--r--src/nvim/eval.c10
-rw-r--r--src/nvim/eval/funcs.c3
-rw-r--r--src/nvim/eval/typval.c3
-rw-r--r--src/nvim/eval/typval.h9
-rw-r--r--src/nvim/eval/userfunc.c5
-rw-r--r--src/nvim/lua/converter.c13
-rw-r--r--src/nvim/lua/converter.h5
-rw-r--r--src/nvim/lua/executor.c192
-rw-r--r--test/functional/lua/luaeval_spec.lua78
9 files changed, 268 insertions, 50 deletions
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 27c2ed1ea5..e1f9fe0253 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -7143,6 +7143,16 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg)
func_ref(name);
callback->data.funcref = vim_strsave(name);
callback->type = kCallbackFuncref;
+ } else if (nlua_is_table_from_lua(arg)) {
+ char_u *name = nlua_register_table_as_callable(arg);
+
+ if (name != NULL) {
+ func_ref(name);
+ callback->data.funcref = vim_strsave(name);
+ callback->type = kCallbackFuncref;
+ } else {
+ r = FAIL;
+ }
} else if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0) {
callback->type = kCallbackNone;
} else {
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 1071e75c06..99014d1a09 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -824,9 +824,12 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
} else if (argvars[0].v_type == VAR_PARTIAL) {
partial = argvars[0].vval.v_partial;
func = partial_name(partial);
+ } else if (nlua_is_table_from_lua(&argvars[0])) {
+ func = nlua_register_table_as_callable(&argvars[0]);
} else {
func = (char_u *)tv_get_string(&argvars[0]);
}
+
if (*func == NUL) {
return; // type error or empty name
}
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 0daaf6c878..89ca2db59b 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -16,6 +16,7 @@
#include "nvim/eval/typval_encode.h"
#include "nvim/eval.h"
#include "nvim/eval/userfunc.h"
+#include "nvim/lua/executor.h"
#include "nvim/types.h"
#include "nvim/assert.h"
#include "nvim/memory.h"
@@ -301,6 +302,7 @@ void tv_list_free_list(list_T *const l)
}
list_log(l, NULL, NULL, "freelist");
+ nlua_free_typval_list(l);
xfree(l);
}
@@ -1374,6 +1376,7 @@ void tv_dict_free_dict(dict_T *const d)
d->dv_used_next->dv_used_prev = d->dv_used_prev;
}
+ nlua_free_typval_dict(d);
xfree(d);
}
diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h
index cb13bec50d..503a32a81e 100644
--- a/src/nvim/eval/typval.h
+++ b/src/nvim/eval/typval.h
@@ -180,6 +180,8 @@ struct listvar_S {
int lv_idx; ///< Index of a cached item, used for optimising repeated l[idx].
int lv_copyID; ///< ID used by deepcopy().
VarLockStatus lv_lock; ///< Zero, VAR_LOCKED, VAR_FIXED.
+
+ LuaRef lua_table_ref;
};
// Static list with 10 items. Use tv_list_init_static10() to initialize.
@@ -245,6 +247,8 @@ struct dictvar_S {
dict_T *dv_used_next; ///< Next dictionary in used dictionaries list.
dict_T *dv_used_prev; ///< Previous dictionary in used dictionaries list.
QUEUE watchers; ///< Dictionary key watchers set by user code.
+
+ LuaRef lua_table_ref;
};
/// Type used for script ID
@@ -271,9 +275,10 @@ typedef struct {
/// Number of fixed variables used for arguments
#define FIXVAR_CNT 12
-/// Callback interface for C function reference
+/// Callback interface for C function reference>
+/// Used for managing functions that were registered with |register_cfunc|
typedef int (*cfunc_T)(int argcount, typval_T *argvars, typval_T *rettv, void *state); // NOLINT
-/// Callback to clear cfunc_T
+/// Callback to clear cfunc_T and any associated state.
typedef void (*cfunc_free_T)(void *state);
// Structure to hold info for a function that is currently being executed.
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index ca2f4a32ca..229f0e8dde 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -3458,7 +3458,6 @@ char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state)
{
char_u *name = get_lambda_name();
ufunc_T *fp = NULL;
- int flags = FC_CFUNC;
fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
if (fp == NULL) {
@@ -3467,7 +3466,7 @@ char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state)
fp->uf_refcount = 1;
fp->uf_varargs = true;
- fp->uf_flags = flags;
+ fp->uf_flags = FC_CFUNC;
fp->uf_calls = 0;
fp->uf_script_ctx = current_sctx;
fp->uf_cb = cb;
@@ -3477,5 +3476,5 @@ char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state)
STRCPY(fp->uf_name, name);
hash_add(&func_hashtab, UF2HIKEY(fp));
- return name;
+ return fp->uf_name;
}
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 9fc4ca7e7e..32e804d213 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -316,6 +316,13 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
break;
}
case LUA_TTABLE: {
+ // Only need to track table refs if we have a metatable associated.
+ LuaRef table_ref = LUA_NOREF;
+ if (lua_getmetatable(lstate, -1)) {
+ lua_pop(lstate, 1);
+ table_ref = nlua_ref(lstate, -1);
+ }
+
const LuaTableProps table_props = nlua_traverse_table(lstate);
for (size_t i = 0; i < kv_size(stack); i++) {
@@ -331,6 +338,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
case kObjectTypeArray: {
cur.tv->v_type = VAR_LIST;
cur.tv->vval.v_list = tv_list_alloc((ptrdiff_t)table_props.maxidx);
+ cur.tv->vval.v_list->lua_table_ref = table_ref;
tv_list_ref(cur.tv->vval.v_list);
if (table_props.maxidx != 0) {
cur.container = true;
@@ -344,6 +352,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
cur.tv->v_type = VAR_DICT;
cur.tv->vval.v_dict = tv_dict_alloc();
cur.tv->vval.v_dict->dv_refcount++;
+ cur.tv->vval.v_dict->lua_table_ref = table_ref;
} else {
cur.special = table_props.has_string_with_nul;
if (table_props.has_string_with_nul) {
@@ -354,11 +363,13 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
S_LEN("_VAL"));
assert(val_di != NULL);
cur.tv = &val_di->di_tv;
+ cur.tv->vval.v_list->lua_table_ref = table_ref;
assert(cur.tv->v_type == VAR_LIST);
} else {
cur.tv->v_type = VAR_DICT;
cur.tv->vval.v_dict = tv_dict_alloc();
cur.tv->vval.v_dict->dv_refcount++;
+ cur.tv->vval.v_dict->lua_table_ref = table_ref;
}
cur.container = true;
cur.idx = lua_gettop(lstate);
@@ -388,8 +399,8 @@ nlua_pop_typval_table_processing_end:
}
case LUA_TFUNCTION: {
LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
-
state->lua_callable.func_ref = nlua_ref(lstate, -1);
+ state->lua_callable.table_ref = LUA_NOREF;
char_u *name = register_cfunc(
&nlua_CFunction_func_call,
diff --git a/src/nvim/lua/converter.h b/src/nvim/lua/converter.h
index e74e3fb084..8601a32418 100644
--- a/src/nvim/lua/converter.h
+++ b/src/nvim/lua/converter.h
@@ -10,11 +10,12 @@
#include "nvim/eval.h"
typedef struct {
- LuaRef func_ref;
+ LuaRef func_ref;
+ LuaRef table_ref;
} LuaCallable;
typedef struct {
- LuaCallable lua_callable;
+ LuaCallable lua_callable;
} LuaCFunctionState;
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 5e924a9f90..9f30609d66 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -53,6 +53,15 @@ typedef struct {
# include "lua/executor.c.generated.h"
#endif
+#define PUSH_ALL_TYPVALS(lstate, args, argcount, special) \
+ for (int i = 0; i < argcount; i++) { \
+ if (args[i].v_type == VAR_UNKNOWN) { \
+ lua_pushnil(lstate); \
+ } else { \
+ nlua_push_typval(lstate, &args[i], special); \
+ } \
+ }
+
/// Convert lua error into a Vim error message
///
/// @param lstate Lua interpreter state.
@@ -700,24 +709,25 @@ int nlua_call(lua_State *lstate)
}
TRY_WRAP({
- // TODO(bfredl): this should be simplified in error handling refactor
- 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`
- // (TRY_WRAP) 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);
+ // TODO(bfredl): this should be simplified in error handling refactor
+ 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`
+ // (TRY_WRAP) 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);
});
free_vim_args:
@@ -839,6 +849,19 @@ void nlua_pushref(lua_State *lstate, LuaRef ref)
lua_rawgeti(lstate, LUA_REGISTRYINDEX, ref);
}
+/// Gets a new reference to an object stored at original_ref
+///
+/// NOTE: It does not copy the value, it creates a new ref to the lua object.
+/// Leaves the stack unchanged.
+LuaRef nlua_newref(lua_State *lstate, LuaRef original_ref)
+{
+ nlua_pushref(lstate, original_ref);
+ LuaRef new_ref = nlua_ref(lstate, -1);
+ lua_pop(lstate, 1);
+
+ return new_ref;
+}
+
/// Evaluate lua string
///
/// Used for luaeval().
@@ -916,13 +939,8 @@ static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name,
return;
}
- for (int i = 0; i < argcount; i++) {
- if (args[i].v_type == VAR_UNKNOWN) {
- lua_pushnil(lstate);
- } else {
- nlua_push_typval(lstate, &args[i], special);
- }
- }
+ PUSH_ALL_TYPVALS(lstate, args, argcount, special);
+
if (lua_pcall(lstate, argcount, ret_tv ? 1 : 0, 0)) {
nlua_error(lstate, _("E5108: Error executing lua %.*s"));
return;
@@ -934,6 +952,14 @@ static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name,
}
/// Call a LuaCallable given some typvals
+///
+/// Used to call any lua callable passed from Lua into VimL
+///
+/// @param[in] lstate Lua State
+/// @param[in] lua_cb Lua Callable
+/// @param[in] argcount Count of typval arguments
+/// @param[in] argvars Typval Arguments
+/// @param[out] rettv The return value from the called function.
int typval_exec_lua_callable(
lua_State *lstate,
LuaCallable lua_cb,
@@ -942,22 +968,32 @@ int typval_exec_lua_callable(
typval_T *rettv
)
{
- LuaRef cb = lua_cb.func_ref;
+ int offset = 0;
+ LuaRef cb = lua_cb.func_ref;
- nlua_pushref(lstate, cb);
+ if (cb == LUA_NOREF) {
+ // This shouldn't happen.
+ luaL_error(lstate, "Invalid function passed to VimL");
+ return ERROR_OTHER;
+ }
- for (int i = 0; i < argcount; i++) {
- nlua_push_typval(lstate, &argvars[i], false);
- }
+ nlua_pushref(lstate, cb);
- if (lua_pcall(lstate, argcount, 1, 0)) {
- luaL_error(lstate, "nlua_CFunction_func_call failed.");
- return ERROR_OTHER;
- }
+ if (lua_cb.table_ref != LUA_NOREF) {
+ offset += 1;
+ nlua_pushref(lstate, lua_cb.table_ref);
+ }
+
+ PUSH_ALL_TYPVALS(lstate, argvars, argcount, false);
+
+ if (lua_pcall(lstate, argcount + offset, 1, 0)) {
+ luaL_error(lstate, "nlua_CFunction_func_call failed.");
+ return ERROR_OTHER;
+ }
- nlua_pop_typval(lstate, rettv);
+ nlua_pop_typval(lstate, rettv);
- return ERROR_NONE;
+ return ERROR_NONE;
}
/// Execute Lua string
@@ -1331,7 +1367,91 @@ void nlua_CFunction_func_free(void *state)
LuaCFunctionState *funcstate = (LuaCFunctionState *)state;
nlua_unref(lstate, funcstate->lua_callable.func_ref);
+ nlua_unref(lstate, funcstate->lua_callable.table_ref);
xfree(funcstate);
}
+bool nlua_is_table_from_lua(typval_T *const arg)
+{
+ if (arg->v_type != VAR_DICT && arg->v_type != VAR_LIST) {
+ return false;
+ }
+
+ if (arg->v_type == VAR_DICT) {
+ return arg->vval.v_dict->lua_table_ref > 0
+ && arg->vval.v_dict->lua_table_ref != LUA_NOREF;
+ } else if (arg->v_type == VAR_LIST) {
+ return arg->vval.v_list->lua_table_ref > 0
+ && arg->vval.v_list->lua_table_ref != LUA_NOREF;
+ }
+
+ return false;
+}
+char_u *nlua_register_table_as_callable(typval_T *const arg)
+{
+ if (!nlua_is_table_from_lua(arg)) {
+ return NULL;
+ }
+
+ LuaRef table_ref;
+ if (arg->v_type == VAR_DICT) {
+ table_ref = arg->vval.v_dict->lua_table_ref;
+ } else if (arg->v_type == VAR_LIST) {
+ table_ref = arg->vval.v_list->lua_table_ref;
+ } else {
+ return NULL;
+ }
+
+ lua_State *const lstate = nlua_enter();
+
+ int top = lua_gettop(lstate);
+
+ nlua_pushref(lstate, table_ref);
+ if (!lua_getmetatable(lstate, -1)) {
+ return NULL;
+ }
+
+ lua_getfield(lstate, -1, "__call");
+ if (!lua_isfunction(lstate, -1)) {
+ return NULL;
+ }
+
+ LuaRef new_table_ref = nlua_newref(lstate, table_ref);
+
+ LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
+ state->lua_callable.func_ref = nlua_ref(lstate, -1);
+ state->lua_callable.table_ref = new_table_ref;
+
+ char_u *name = register_cfunc(
+ &nlua_CFunction_func_call,
+ &nlua_CFunction_func_free,
+ state);
+
+
+ lua_pop(lstate, 3);
+ assert(top == lua_gettop(lstate));
+
+ return name;
+}
+
+/// Helper function to free a list_T
+void nlua_free_typval_list(list_T *const l)
+{
+ if (l->lua_table_ref != LUA_NOREF && l->lua_table_ref > 0) {
+ lua_State *const lstate = nlua_enter();
+ nlua_unref(lstate, l->lua_table_ref);
+ l->lua_table_ref = LUA_NOREF;
+ }
+}
+
+
+/// Helper function to free a dict_T
+void nlua_free_typval_dict(dict_T *const d)
+{
+ if (d->lua_table_ref != LUA_NOREF && d->lua_table_ref > 0) {
+ lua_State *const lstate = nlua_enter();
+ nlua_unref(lstate, d->lua_table_ref);
+ d->lua_table_ref = LUA_NOREF;
+ }
+}
diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua
index 4faacecd86..964ea4561e 100644
--- a/test/functional/lua/luaeval_spec.lua
+++ b/test/functional/lua/luaeval_spec.lua
@@ -295,13 +295,12 @@ describe('luaeval()', function()
]])
end)
- -- TODO(tjdevries): Need to figure
- pending('should work with metatables using __call', function()
- eq(true, exec_lua [[
+ it('should work with metatables using __call', function()
+ eq(1, exec_lua [[
local this_is_local_variable = false
- local callable_table = setmetatable({}, {
- __call = function(...)
- this_is_local_variable = true
+ local callable_table = setmetatable({x = 1}, {
+ __call = function(t, ...)
+ this_is_local_variable = t.x
end
})
@@ -315,6 +314,73 @@ describe('luaeval()', function()
]])
end)
+ it('should handle being called from a timer once.', function()
+ eq(3, exec_lua [[
+ local this_is_local_variable = false
+ local callable_table = setmetatable({5, 4, 3, 2, 1}, {
+ __call = function(t, ...) this_is_local_variable = t[3] end
+ })
+
+ vim.fn.timer_start(5, callable_table)
+ vim.wait(1000, function()
+ return this_is_local_variable
+ end)
+
+ return this_is_local_variable
+ ]])
+ end)
+
+ it('should call functions once with __call metamethod', function()
+ eq(true, exec_lua [[
+ local this_is_local_variable = false
+ local callable_table = setmetatable({a = true, b = false}, {
+ __call = function(t, ...) this_is_local_variable = t.a end
+ })
+
+ assert(getmetatable(callable_table).__call)
+ vim.fn.call(callable_table, {})
+
+ return this_is_local_variable
+ ]])
+ end)
+
+ it('should work with lists using __call', function()
+ eq(3, exec_lua [[
+ local this_is_local_variable = false
+ local mt = {
+ __call = function(t, ...)
+ this_is_local_variable = t[3]
+ end
+ }
+ local callable_table = setmetatable({5, 4, 3, 2, 1}, mt)
+
+ -- Call it once...
+ vim.fn.timer_start(5, callable_table)
+ vim.wait(1000, function()
+ return this_is_local_variable
+ end)
+
+ assert(this_is_local_variable)
+ this_is_local_variable = false
+
+ vim.fn.timer_start(5, callable_table)
+ vim.wait(1000, function()
+ return this_is_local_variable
+ end)
+
+ return this_is_local_variable
+ ]])
+ end)
+
+ it('should not work with tables not using __call', function()
+ eq({false, 'Vim:E921: Invalid callback argument'}, exec_lua [[
+ local this_is_local_variable = false
+ local callable_table = setmetatable({x = 1}, {})
+
+ return {pcall(function() vim.fn.timer_start(5, callable_table) end)}
+ ]])
+ end)
+
it('correctly converts containers with type_idx', function()
eq(5, eval('type(luaeval("{[vim.type_idx]=vim.types.float, [vim.val_idx]=0}"))'))
eq(4, eval([[type(luaeval('{[vim.type_idx]=vim.types.dictionary}'))]]))