forked from aniani/vim
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)
This commit is contained in:
@@ -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*
|
||||
|
99
src/if_lua.c
99
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)
|
||||
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -754,6 +754,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
1054,
|
||||
/**/
|
||||
1053,
|
||||
/**/
|
||||
|
Reference in New Issue
Block a user