# HG changeset patch # User Bram Moolenaar # Date 1593106221 -7200 # Node ID ae185f35e256500a54bde53590179f6ecb25ffa0 # Parent 3f0abea9bed2693aa4f15c45065483121f44f61d 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 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) diff --git a/runtime/doc/if_lua.txt b/runtime/doc/if_lua.txt --- 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 <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 diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro --- 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); diff --git a/src/structs.h b/src/structs.h --- 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 diff --git a/src/testdir/test_lua.vim b/src/testdir/test_lua.vim --- 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 <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) diff --git a/src/version.c b/src/version.c --- 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,