summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/if_lua.txt8
-rw-r--r--src/if_lua.c101
-rw-r--r--src/proto/userfunc.pro1
-rw-r--r--src/structs.h9
-rw-r--r--src/testdir/test_lua.vim29
-rw-r--r--src/userfunc.c64
-rw-r--r--src/version.c2
7 files changed, 213 insertions, 1 deletions
diff --git a/runtime/doc/if_lua.txt b/runtime/doc/if_lua.txt
index 63e227d303..170f861ff0 100644
--- 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*
diff --git a/src/if_lua.c b/src/if_lua.c
index 75231b4c27..ce0901a20e 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 6ed79ba035..340ef57f1c 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 3950887355..e308ff448d 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 bd50bce564..826a7bce80 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 c6a8a8cd3d..691c55c4fd 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 c1957e41a6..536d27d44c 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,