diff options
author | Bram Moolenaar <Bram@vim.org> | 2020-06-25 19:27:56 +0200 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2020-06-25 19:27:56 +0200 |
commit | 801ab069341c8652680d63c174530fd4feb2911e (patch) | |
tree | ff8d365fe4c9ea1dbd5b5918b3a58568a77ec18e /src | |
parent | 832adf9bb8cd39d8e982d8a35ed8a6d39b974494 (diff) | |
download | vim-git-801ab069341c8652680d63c174530fd4feb2911e.tar.gz |
patch 8.2.1054: not so easy to pass a lua function to Vimv8.2.1054
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)
Diffstat (limited to 'src')
-rw-r--r-- | src/if_lua.c | 101 | ||||
-rw-r--r-- | src/proto/userfunc.pro | 1 | ||||
-rw-r--r-- | src/structs.h | 9 | ||||
-rw-r--r-- | src/testdir/test_lua.vim | 29 | ||||
-rw-r--r-- | src/userfunc.c | 64 | ||||
-rw-r--r-- | src/version.c | 2 |
6 files changed, 205 insertions, 1 deletions
diff --git a/src/if_lua.c b/src/if_lua.c index 75231b4c2..ce0901a20 100644 --- 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_free"; 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[] = "luaV_setref"; #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_State *L, list_T *lis); 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, typval_T *tv) 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 diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro index 6ed79ba03..340ef57f1 100644 --- 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 index 395088735..e308ff448 100644 --- 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 index bd50bce56..826a7bce8 100644 --- 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 diff --git a/src/userfunc.c b/src/userfunc.c index c6a8a8cd3..691c55c4f 100644 --- 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) diff --git a/src/version.c b/src/version.c index c1957e41a..536d27d44 100644 --- 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, |