patch 8.2.1054: not so easy to pass a lua function to Vim
Problem: Not so easy to pass a lua function to Vim.
Solution: Convert a Lua function and closure to a Vim funcref. (Prabir
Shrestha, closes #6246)
diff --git a/src/if_lua.c b/src/if_lua.c
index 75231b4..ce0901a 100644
--- a/src/if_lua.c
+++ b/src/if_lua.c
@@ -35,6 +35,13 @@
} luaV_Funcref;
typedef void (*msgfunc_T)(char_u *);
+typedef struct {
+ int lua_funcref; // ref to a lua func
+ int lua_tableref; // ref to a lua table if metatable else LUA_NOREF. used
+ // for __call
+ lua_State *L;
+} luaV_CFuncState;
+
static const char LUAVIM_DICT[] = "dict";
static const char LUAVIM_LIST[] = "list";
static const char LUAVIM_BLOB[] = "blob";
@@ -45,6 +52,8 @@
static const char LUAVIM_LUAEVAL[] = "luaV_luaeval";
static const char LUAVIM_SETREF[] = "luaV_setref";
+static const char LUA___CALL[] = "__call";
+
// most functions are closures with a cache table as first upvalue;
// get/setudata manage references to vim userdata in cache table through
// object pointers (light userdata)
@@ -64,7 +73,7 @@
#define luaV_emsg(L) luaV_msgfunc((L), (msgfunc_T) emsg)
#define luaV_checktypval(L, a, v, msg) \
do { \
- if (luaV_totypval(L, a, v) == FAIL) \
+ if (luaV_totypval(L, a, v) == FAIL) \
luaL_error(L, msg ": cannot convert value"); \
} while (0)
@@ -72,6 +81,8 @@
static luaV_Dict *luaV_pushdict(lua_State *L, dict_T *dic);
static luaV_Blob *luaV_pushblob(lua_State *L, blob_T *blo);
static luaV_Funcref *luaV_pushfuncref(lua_State *L, char_u *name);
+static int luaV_call_lua_func(int argcount, typval_T *argvars, typval_T *rettv, void *state);
+static void luaV_call_lua_func_free(void *state);
#if LUA_VERSION_NUM <= 501
#define luaV_openlib(L, l, n) luaL_openlib(L, NULL, l, n)
@@ -591,6 +602,45 @@
tv->vval.v_number = (varnumber_T) lua_tointeger(L, pos);
#endif
break;
+ case LUA_TFUNCTION:
+ {
+ char_u *name;
+ lua_pushvalue(L, pos);
+ luaV_CFuncState *state = ALLOC_CLEAR_ONE(luaV_CFuncState);
+ state->lua_funcref = luaL_ref(L, LUA_REGISTRYINDEX);
+ state->L = L;
+ state->lua_tableref = LUA_NOREF;
+ name = register_cfunc(&luaV_call_lua_func,
+ &luaV_call_lua_func_free, state);
+ tv->v_type = VAR_FUNC;
+ tv->vval.v_string = vim_strsave(name);
+ break;
+ }
+ case LUA_TTABLE:
+ {
+ lua_pushvalue(L, pos);
+ int lua_tableref = luaL_ref(L, LUA_REGISTRYINDEX);
+ if (lua_getmetatable(L, pos)) {
+ lua_getfield(L, -1, LUA___CALL);
+ if (lua_isfunction(L, -1)) {
+ char_u *name;
+ int lua_funcref = luaL_ref(L, LUA_REGISTRYINDEX);
+ luaV_CFuncState *state = ALLOC_CLEAR_ONE(luaV_CFuncState);
+ state->lua_funcref = lua_funcref;
+ state->L = L;
+ state->lua_tableref = lua_tableref;
+ name = register_cfunc(&luaV_call_lua_func,
+ &luaV_call_lua_func_free, state);
+ tv->v_type = VAR_FUNC;
+ tv->vval.v_string = vim_strsave(name);
+ break;
+ }
+ }
+ tv->v_type = VAR_NUMBER;
+ tv->vval.v_number = 0;
+ status = FAIL;
+ break;
+ }
case LUA_TUSERDATA:
{
void *p = lua_touserdata(L, pos);
@@ -2415,4 +2465,53 @@
}
}
+/*
+ * Native C function callback
+ */
+ static int
+luaV_call_lua_func(
+ int argcount,
+ typval_T *argvars,
+ typval_T *rettv,
+ void *state)
+{
+ int i;
+ int luaargcount = argcount;
+ luaV_CFuncState *funcstate = (luaV_CFuncState*)state;
+ lua_rawgeti(funcstate->L, LUA_REGISTRYINDEX, funcstate->lua_funcref);
+
+ if (funcstate->lua_tableref != LUA_NOREF)
+ {
+ // First arg for metatable __call method is a table
+ luaargcount += 1;
+ lua_rawgeti(funcstate->L, LUA_REGISTRYINDEX, funcstate->lua_tableref);
+ }
+
+ for (i = 0; i < argcount; ++i)
+ luaV_pushtypval(funcstate->L, &argvars[i]);
+
+ if (lua_pcall(funcstate->L, luaargcount, 1, 0))
+ {
+ luaV_emsg(funcstate->L);
+ return FCERR_OTHER;
+ }
+
+ luaV_checktypval(funcstate->L, -1, rettv, "get return value");
+ return FCERR_NONE;
+}
+
+/*
+ * Free up any lua references held by the func state.
+ */
+ static void
+luaV_call_lua_func_free(void *state)
+{
+ luaV_CFuncState *funcstate = (luaV_CFuncState*)state;
+ luaL_unref(L, LUA_REGISTRYINDEX, funcstate->lua_funcref);
+ funcstate->L = NULL;
+ if (funcstate->lua_tableref != LUA_NOREF)
+ luaL_unref(L, LUA_REGISTRYINDEX, funcstate->lua_tableref);
+ VIM_CLEAR(funcstate);
+}
+
#endif