mirror of
https://github.com/vim/vim.git
synced 2025-09-24 03:44:06 -04:00
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 l = d.len -- assign d as 'self'
|
||||||
:lua print(l())
|
: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*
|
7. Buffer userdata *lua-buffer*
|
||||||
|
99
src/if_lua.c
99
src/if_lua.c
@@ -35,6 +35,13 @@ typedef struct {
|
|||||||
} luaV_Funcref;
|
} luaV_Funcref;
|
||||||
typedef void (*msgfunc_T)(char_u *);
|
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_DICT[] = "dict";
|
||||||
static const char LUAVIM_LIST[] = "list";
|
static const char LUAVIM_LIST[] = "list";
|
||||||
static const char LUAVIM_BLOB[] = "blob";
|
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_LUAEVAL[] = "luaV_luaeval";
|
||||||
static const char LUAVIM_SETREF[] = "luaV_setref";
|
static const char LUAVIM_SETREF[] = "luaV_setref";
|
||||||
|
|
||||||
|
static const char LUA___CALL[] = "__call";
|
||||||
|
|
||||||
// most functions are closures with a cache table as first upvalue;
|
// most functions are closures with a cache table as first upvalue;
|
||||||
// get/setudata manage references to vim userdata in cache table through
|
// get/setudata manage references to vim userdata in cache table through
|
||||||
// object pointers (light userdata)
|
// 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_Dict *luaV_pushdict(lua_State *L, dict_T *dic);
|
||||||
static luaV_Blob *luaV_pushblob(lua_State *L, blob_T *blo);
|
static luaV_Blob *luaV_pushblob(lua_State *L, blob_T *blo);
|
||||||
static luaV_Funcref *luaV_pushfuncref(lua_State *L, char_u *name);
|
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
|
#if LUA_VERSION_NUM <= 501
|
||||||
#define luaV_openlib(L, l, n) luaL_openlib(L, NULL, l, n)
|
#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);
|
tv->vval.v_number = (varnumber_T) lua_tointeger(L, pos);
|
||||||
#endif
|
#endif
|
||||||
break;
|
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:
|
case LUA_TUSERDATA:
|
||||||
{
|
{
|
||||||
void *p = lua_touserdata(L, pos);
|
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
|
#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);
|
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);
|
char_u *get_lambda_name(void);
|
||||||
int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate);
|
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);
|
char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload);
|
||||||
void emsg_funcname(char *ermsg, char_u *name);
|
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);
|
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
|
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)
|
#if defined(FEAT_EVAL) || defined(PROTO)
|
||||||
typedef struct funccall_S funccall_T;
|
typedef struct funccall_S funccall_T;
|
||||||
|
|
||||||
@@ -1562,6 +1565,11 @@ typedef struct
|
|||||||
char_u *uf_va_name; // name from "...name" or NULL
|
char_u *uf_va_name; // name from "...name" or NULL
|
||||||
type_T *uf_va_type; // type from "...name: type" 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
|
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
|
garray_T uf_lines; // function lines
|
||||||
# ifdef FEAT_PROFILE
|
# ifdef FEAT_PROFILE
|
||||||
@@ -1607,6 +1615,7 @@ typedef struct
|
|||||||
#define FC_EXPORT 0x100 // "export def Func()"
|
#define FC_EXPORT 0x100 // "export def Func()"
|
||||||
#define FC_NOARGS 0x200 // no a: variables in lambda
|
#define FC_NOARGS 0x200 // no a: variables in lambda
|
||||||
#define FC_VIM9 0x400 // defined in vim9 script file
|
#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 MAX_FUNC_ARGS 20 // maximum number of function arguments
|
||||||
#define VAR_SHORT_LEN 20 // short variable name length
|
#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()"))
|
call assert_equal("hello from lua", luaeval("require('testluaplugin').hello()"))
|
||||||
endfunc
|
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()
|
" Test vim.line()
|
||||||
func Test_lua_line()
|
func Test_lua_line()
|
||||||
new
|
new
|
||||||
|
@@ -341,6 +341,51 @@ get_lambda_name(void)
|
|||||||
return name;
|
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".
|
* Parse a lambda expression and get a Funcref from "*arg".
|
||||||
* Return OK or FAIL. Returns NOTDONE for dict or {expr}.
|
* 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)
|
vim_free(((type_T **)fp->uf_type_list.ga_data)
|
||||||
[--fp->uf_type_list.ga_len]);
|
[--fp->uf_type_list.ga_len]);
|
||||||
ga_clear(&fp->uf_type_list);
|
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
|
#ifdef FEAT_PROFILE
|
||||||
VIM_CLEAR(fp->uf_tml_count);
|
VIM_CLEAR(fp->uf_tml_count);
|
||||||
VIM_CLEAR(fp->uf_tml_total);
|
VIM_CLEAR(fp->uf_tml_total);
|
||||||
@@ -1973,6 +2029,14 @@ call_func(
|
|||||||
|
|
||||||
if (fp != NULL && (fp->uf_flags & FC_DELETED))
|
if (fp != NULL && (fp->uf_flags & FC_DELETED))
|
||||||
error = FCERR_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)
|
else if (fp != NULL)
|
||||||
{
|
{
|
||||||
if (funcexe->argv_func != NULL)
|
if (funcexe->argv_func != NULL)
|
||||||
|
@@ -754,6 +754,8 @@ static char *(features[]) =
|
|||||||
|
|
||||||
static int included_patches[] =
|
static int included_patches[] =
|
||||||
{ /* Add new patch number below this line */
|
{ /* Add new patch number below this line */
|
||||||
|
/**/
|
||||||
|
1054,
|
||||||
/**/
|
/**/
|
||||||
1053,
|
1053,
|
||||||
/**/
|
/**/
|
||||||
|
Reference in New Issue
Block a user