changeset 21006:ae185f35e256 v8.2.1054

patch 8.2.1054: not so easy to pass a lua function to Vim Commit: https://github.com/vim/vim/commit/801ab069341c8652680d63c174530fd4feb2911e Author: Bram Moolenaar <Bram@vim.org> Date: Thu Jun 25 19:27:56 2020 +0200 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)
author Bram Moolenaar <Bram@vim.org>
date Thu, 25 Jun 2020 19:30:21 +0200
parents 3f0abea9bed2
children dc8f29bb3272
files runtime/doc/if_lua.txt src/if_lua.c src/proto/userfunc.pro src/structs.h src/testdir/test_lua.vim src/userfunc.c src/version.c
diffstat 7 files changed, 213 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/if_lua.txt
+++ b/runtime/doc/if_lua.txt
@@ -333,6 +333,14 @@ Examples:
 	:lua l = d.len -- assign d as 'self'
 	:lua print(l())
 <
+Lua functions and closures are automatically converted to a Vim |Funcref| and
+can be accessed in Vim scripts.  Example:
+>
+	lua <<EOF
+	vim.fn.timer_start(1000, function(timer)
+	    print('timer callback')
+	end)
+	EOF
 
 ==============================================================================
 7. Buffer userdata					*lua-buffer*
--- a/src/if_lua.c
+++ b/src/if_lua.c
@@ -35,6 +35,13 @@ typedef struct {
 } 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_FREE[] = "luaV_
 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 @@ static const char LUAVIM_SETREF[] = "lua
 #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_List *luaV_pushlist(lua_Stat
 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 @@ luaV_totypval(lua_State *L, int pos, typ
 	    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 @@ update_package_paths_in_lua()
     }
 }
 
+/*
+ * 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
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -4,6 +4,7 @@ hashtab_T *func_tbl_get(void);
 int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, garray_T *argtypes, int *varargs, garray_T *default_args, int skip, exarg_T *eap, char_u **line_to_free);
 char_u *get_lambda_name(void);
 int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate);
+char_u *register_cfunc(cfunc_T cb, cfunc_free_T free_cb, void *state);
 char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload);
 void emsg_funcname(char *ermsg, char_u *name);
 int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, funcexe_T *funcexe);
--- a/src/structs.h
+++ b/src/structs.h
@@ -1529,6 +1529,9 @@ struct blobvar_S
     char	bv_lock;	// zero, VAR_LOCKED, VAR_FIXED
 };
 
+typedef int (*cfunc_T)(int argcount, typval_T *argvars, typval_T *rettv, void *state);
+typedef void (*cfunc_free_T)(void *state);
+
 #if defined(FEAT_EVAL) || defined(PROTO)
 typedef struct funccall_S funccall_T;
 
@@ -1562,6 +1565,11 @@ typedef struct
     char_u	*uf_va_name;	// name from "...name" or NULL
     type_T	*uf_va_type;	// type from "...name: type" or NULL
     type_T	*uf_func_type;	// type of the function, &t_func_any if unknown
+# if defined(FEAT_LUA)
+    cfunc_T     uf_cb;		// callback function for cfunc
+    cfunc_free_T uf_cb_free;    // callback function to free cfunc
+    void        *uf_cb_state;   // state of uf_cb
+# endif
 
     garray_T	uf_lines;	// function lines
 # ifdef FEAT_PROFILE
@@ -1607,6 +1615,7 @@ typedef struct
 #define FC_EXPORT   0x100	// "export def Func()"
 #define FC_NOARGS   0x200	// no a: variables in lambda
 #define FC_VIM9	    0x400	// defined in vim9 script file
+#define FC_CFUNC    0x800	// defined as Lua C func
 
 #define MAX_FUNC_ARGS	20	// maximum number of function arguments
 #define VAR_SHORT_LEN	20	// short variable name length
--- a/src/testdir/test_lua.vim
+++ b/src/testdir/test_lua.vim
@@ -541,6 +541,35 @@ func Test_update_package_paths()
   call assert_equal("hello from lua", luaeval("require('testluaplugin').hello()"))
 endfunc
 
+func Vim_func_call_lua_callback(Concat, Cb)
+  let l:message = a:Concat("hello", "vim")
+  call a:Cb(l:message)
+endfunc
+
+func Test_pass_lua_callback_to_vim_from_lua()
+  lua pass_lua_callback_to_vim_from_lua_result = ""
+  call assert_equal("", luaeval("pass_lua_callback_to_vim_from_lua_result"))
+  lua <<EOF
+  vim.funcref('Vim_func_call_lua_callback')(
+    function(greeting, message)
+      return greeting .. " " .. message
+    end,
+    function(message)
+      pass_lua_callback_to_vim_from_lua_result = message
+    end)
+EOF
+  call assert_equal("hello vim", luaeval("pass_lua_callback_to_vim_from_lua_result"))
+endfunc
+
+func Vim_func_call_metatable_lua_callback(Greet)
+  return a:Greet("world")
+endfunc
+
+func Test_pass_lua_metatable_callback_to_vim_from_lua()
+  let result = luaeval("vim.funcref('Vim_func_call_metatable_lua_callback')(setmetatable({ space = ' '}, { __call = function(tbl, msg) return 'hello' .. tbl.space .. msg  end }) )")
+  call assert_equal("hello world", result)
+endfunc
+
 " Test vim.line()
 func Test_lua_line()
   new
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -341,6 +341,51 @@ get_lambda_name(void)
     return name;
 }
 
+#if defined(FEAT_LUA) || defined(PROTO)
+/*
+ * Registers a native C callback which can be called from Vim script.
+ * Returns the name of the Vim script function.
+ */
+    char_u *
+register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state)
+{
+    char_u	*name = get_lambda_name();
+    ufunc_T	*fp = NULL;
+    garray_T	newargs;
+    garray_T	newlines;
+
+    ga_init(&newargs);
+    ga_init(&newlines);
+
+    fp = alloc_clear(offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
+    if (fp == NULL)
+        goto errret;
+
+    fp->uf_dfunc_idx = UF_NOT_COMPILED;
+    fp->uf_refcount = 1;
+    fp->uf_varargs = TRUE;
+    fp->uf_flags = FC_CFUNC;
+    fp->uf_calls = 0;
+    fp->uf_script_ctx = current_sctx;
+    fp->uf_lines = newlines;
+    fp->uf_args = newargs;
+    fp->uf_cb = cb;
+    fp->uf_cb_free = cb_free;
+    fp->uf_cb_state = state;
+
+    set_ufunc_name(fp, name);
+    hash_add(&func_hashtab, UF2HIKEY(fp));
+
+    return name;
+
+errret:
+    ga_clear_strings(&newargs);
+    ga_clear_strings(&newlines);
+    vim_free(fp);
+    return NULL;
+}
+#endif
+
 /*
  * Parse a lambda expression and get a Funcref from "*arg".
  * Return OK or FAIL.  Returns NOTDONE for dict or {expr}.
@@ -1027,6 +1072,17 @@ func_clear_items(ufunc_T *fp)
 	vim_free(((type_T **)fp->uf_type_list.ga_data)
 						  [--fp->uf_type_list.ga_len]);
     ga_clear(&fp->uf_type_list);
+
+#ifdef FEAT_LUA
+    if (fp->uf_cb_free != NULL)
+    {
+	fp->uf_cb_free(fp->uf_cb_state);
+	fp->uf_cb_free = NULL;
+    }
+
+    fp->uf_cb_state = NULL;
+    fp->uf_cb = NULL;
+#endif
 #ifdef FEAT_PROFILE
     VIM_CLEAR(fp->uf_tml_count);
     VIM_CLEAR(fp->uf_tml_total);
@@ -1973,6 +2029,14 @@ call_func(
 
 	    if (fp != NULL && (fp->uf_flags & FC_DELETED))
 		error = FCERR_DELETED;
+#ifdef FEAT_LUA
+	    else if (fp != NULL && (fp->uf_flags & FC_CFUNC))
+	    {
+		cfunc_T cb = fp->uf_cb;
+
+		error = (*cb)(argcount, argvars, rettv, fp->uf_cb_state);
+	    }
+#endif
 	    else if (fp != NULL)
 	    {
 		if (funcexe->argv_func != NULL)
--- a/src/version.c
+++ b/src/version.c
@@ -755,6 +755,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1054,
+/**/
     1053,
 /**/
     1052,