mirror of
https://github.com/neovim/neovim.git
synced 2025-09-07 11:58:17 +00:00
Merge pull request #12507 from tjdevries/tjdevries/viml_lua_callbacks
[RFC] Allow passing lua functions and closures into vim functions.
This commit is contained in:
@@ -7143,6 +7143,16 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg)
|
|||||||
func_ref(name);
|
func_ref(name);
|
||||||
callback->data.funcref = vim_strsave(name);
|
callback->data.funcref = vim_strsave(name);
|
||||||
callback->type = kCallbackFuncref;
|
callback->type = kCallbackFuncref;
|
||||||
|
} else if (nlua_is_table_from_lua(arg)) {
|
||||||
|
char_u *name = nlua_register_table_as_callable(arg);
|
||||||
|
|
||||||
|
if (name != NULL) {
|
||||||
|
func_ref(name);
|
||||||
|
callback->data.funcref = vim_strsave(name);
|
||||||
|
callback->type = kCallbackFuncref;
|
||||||
|
} else {
|
||||||
|
r = FAIL;
|
||||||
|
}
|
||||||
} else if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0) {
|
} else if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0) {
|
||||||
callback->type = kCallbackNone;
|
callback->type = kCallbackNone;
|
||||||
} else {
|
} else {
|
||||||
|
@@ -824,9 +824,12 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|||||||
} else if (argvars[0].v_type == VAR_PARTIAL) {
|
} else if (argvars[0].v_type == VAR_PARTIAL) {
|
||||||
partial = argvars[0].vval.v_partial;
|
partial = argvars[0].vval.v_partial;
|
||||||
func = partial_name(partial);
|
func = partial_name(partial);
|
||||||
|
} else if (nlua_is_table_from_lua(&argvars[0])) {
|
||||||
|
func = nlua_register_table_as_callable(&argvars[0]);
|
||||||
} else {
|
} else {
|
||||||
func = (char_u *)tv_get_string(&argvars[0]);
|
func = (char_u *)tv_get_string(&argvars[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*func == NUL) {
|
if (*func == NUL) {
|
||||||
return; // type error or empty name
|
return; // type error or empty name
|
||||||
}
|
}
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
#include "nvim/eval/typval_encode.h"
|
#include "nvim/eval/typval_encode.h"
|
||||||
#include "nvim/eval.h"
|
#include "nvim/eval.h"
|
||||||
#include "nvim/eval/userfunc.h"
|
#include "nvim/eval/userfunc.h"
|
||||||
|
#include "nvim/lua/executor.h"
|
||||||
#include "nvim/types.h"
|
#include "nvim/types.h"
|
||||||
#include "nvim/assert.h"
|
#include "nvim/assert.h"
|
||||||
#include "nvim/memory.h"
|
#include "nvim/memory.h"
|
||||||
@@ -301,6 +302,7 @@ void tv_list_free_list(list_T *const l)
|
|||||||
}
|
}
|
||||||
list_log(l, NULL, NULL, "freelist");
|
list_log(l, NULL, NULL, "freelist");
|
||||||
|
|
||||||
|
nlua_free_typval_list(l);
|
||||||
xfree(l);
|
xfree(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1374,6 +1376,7 @@ void tv_dict_free_dict(dict_T *const d)
|
|||||||
d->dv_used_next->dv_used_prev = d->dv_used_prev;
|
d->dv_used_next->dv_used_prev = d->dv_used_prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nlua_free_typval_dict(d);
|
||||||
xfree(d);
|
xfree(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -180,6 +180,8 @@ struct listvar_S {
|
|||||||
int lv_idx; ///< Index of a cached item, used for optimising repeated l[idx].
|
int lv_idx; ///< Index of a cached item, used for optimising repeated l[idx].
|
||||||
int lv_copyID; ///< ID used by deepcopy().
|
int lv_copyID; ///< ID used by deepcopy().
|
||||||
VarLockStatus lv_lock; ///< Zero, VAR_LOCKED, VAR_FIXED.
|
VarLockStatus lv_lock; ///< Zero, VAR_LOCKED, VAR_FIXED.
|
||||||
|
|
||||||
|
LuaRef lua_table_ref;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Static list with 10 items. Use tv_list_init_static10() to initialize.
|
// Static list with 10 items. Use tv_list_init_static10() to initialize.
|
||||||
@@ -245,6 +247,8 @@ struct dictvar_S {
|
|||||||
dict_T *dv_used_next; ///< Next dictionary in used dictionaries list.
|
dict_T *dv_used_next; ///< Next dictionary in used dictionaries list.
|
||||||
dict_T *dv_used_prev; ///< Previous dictionary in used dictionaries list.
|
dict_T *dv_used_prev; ///< Previous dictionary in used dictionaries list.
|
||||||
QUEUE watchers; ///< Dictionary key watchers set by user code.
|
QUEUE watchers; ///< Dictionary key watchers set by user code.
|
||||||
|
|
||||||
|
LuaRef lua_table_ref;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Type used for script ID
|
/// Type used for script ID
|
||||||
@@ -271,6 +275,12 @@ typedef struct {
|
|||||||
/// Number of fixed variables used for arguments
|
/// Number of fixed variables used for arguments
|
||||||
#define FIXVAR_CNT 12
|
#define FIXVAR_CNT 12
|
||||||
|
|
||||||
|
/// Callback interface for C function reference>
|
||||||
|
/// Used for managing functions that were registered with |register_cfunc|
|
||||||
|
typedef int (*cfunc_T)(int argcount, typval_T *argvars, typval_T *rettv, void *state); // NOLINT
|
||||||
|
/// Callback to clear cfunc_T and any associated state.
|
||||||
|
typedef void (*cfunc_free_T)(void *state);
|
||||||
|
|
||||||
// Structure to hold info for a function that is currently being executed.
|
// Structure to hold info for a function that is currently being executed.
|
||||||
typedef struct funccall_S funccall_T;
|
typedef struct funccall_S funccall_T;
|
||||||
|
|
||||||
@@ -307,6 +317,10 @@ struct ufunc {
|
|||||||
garray_T uf_lines; ///< function lines
|
garray_T uf_lines; ///< function lines
|
||||||
int uf_profiling; ///< true when func is being profiled
|
int uf_profiling; ///< true when func is being profiled
|
||||||
int uf_prof_initialized;
|
int uf_prof_initialized;
|
||||||
|
// Managing cfuncs
|
||||||
|
cfunc_T uf_cb; ///< C function extension callback
|
||||||
|
cfunc_free_T uf_cb_free; ///< C function extesion free callback
|
||||||
|
void *uf_cb_state; ///< State of C function extension.
|
||||||
// Profiling the function as a whole.
|
// Profiling the function as a whole.
|
||||||
int uf_tm_count; ///< nr of calls
|
int uf_tm_count; ///< nr of calls
|
||||||
proftime_T uf_tm_total; ///< time spent in function + children
|
proftime_T uf_tm_total; ///< time spent in function + children
|
||||||
|
@@ -32,6 +32,7 @@
|
|||||||
#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0
|
#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0
|
||||||
#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0
|
#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0
|
||||||
#define FC_SANDBOX 0x40 // function defined in the sandbox
|
#define FC_SANDBOX 0x40 // function defined in the sandbox
|
||||||
|
#define FC_CFUNC 0x80 // C function extension
|
||||||
|
|
||||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
#include "eval/userfunc.c.generated.h"
|
#include "eval/userfunc.c.generated.h"
|
||||||
@@ -162,6 +163,17 @@ static void register_closure(ufunc_T *fp)
|
|||||||
[current_funccal->fc_funcs.ga_len++] = fp;
|
[current_funccal->fc_funcs.ga_len++] = fp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Get a name for a lambda. Returned in static memory.
|
||||||
|
char_u * get_lambda_name(void)
|
||||||
|
{
|
||||||
|
static char_u name[30];
|
||||||
|
static int lambda_no = 0;
|
||||||
|
|
||||||
|
snprintf((char *)name, sizeof(name), "<lambda>%d", ++lambda_no);
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
/// 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}.
|
||||||
@@ -175,7 +187,6 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
|
|||||||
int ret;
|
int ret;
|
||||||
char_u *start = skipwhite(*arg + 1);
|
char_u *start = skipwhite(*arg + 1);
|
||||||
char_u *s, *e;
|
char_u *s, *e;
|
||||||
static int lambda_no = 0;
|
|
||||||
bool *old_eval_lavars = eval_lavars_used;
|
bool *old_eval_lavars = eval_lavars_used;
|
||||||
bool eval_lavars = false;
|
bool eval_lavars = false;
|
||||||
|
|
||||||
@@ -219,11 +230,9 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
|
|||||||
if (evaluate) {
|
if (evaluate) {
|
||||||
int len, flags = 0;
|
int len, flags = 0;
|
||||||
char_u *p;
|
char_u *p;
|
||||||
char_u name[20];
|
|
||||||
garray_T newlines;
|
garray_T newlines;
|
||||||
|
|
||||||
lambda_no++;
|
char_u *name = get_lambda_name();
|
||||||
snprintf((char *)name, sizeof(name), "<lambda>%d", lambda_no);
|
|
||||||
|
|
||||||
fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
|
fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
|
||||||
pt = xcalloc(1, sizeof(partial_T));
|
pt = xcalloc(1, sizeof(partial_T));
|
||||||
@@ -700,6 +709,11 @@ static void func_clear_items(ufunc_T *fp)
|
|||||||
ga_clear_strings(&(fp->uf_args));
|
ga_clear_strings(&(fp->uf_args));
|
||||||
ga_clear_strings(&(fp->uf_lines));
|
ga_clear_strings(&(fp->uf_lines));
|
||||||
|
|
||||||
|
if (fp->uf_cb_free != NULL) {
|
||||||
|
fp->uf_cb_free(fp->uf_cb_state);
|
||||||
|
fp->uf_cb_free = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
XFREE_CLEAR(fp->uf_tml_count);
|
XFREE_CLEAR(fp->uf_tml_count);
|
||||||
XFREE_CLEAR(fp->uf_tml_total);
|
XFREE_CLEAR(fp->uf_tml_total);
|
||||||
XFREE_CLEAR(fp->uf_tml_self);
|
XFREE_CLEAR(fp->uf_tml_self);
|
||||||
@@ -1408,6 +1422,9 @@ call_func(
|
|||||||
|
|
||||||
if (fp != NULL && (fp->uf_flags & FC_DELETED)) {
|
if (fp != NULL && (fp->uf_flags & FC_DELETED)) {
|
||||||
error = ERROR_DELETED;
|
error = ERROR_DELETED;
|
||||||
|
} else if (fp != NULL && (fp->uf_flags & FC_CFUNC)) {
|
||||||
|
cfunc_T cb = fp->uf_cb;
|
||||||
|
error = (*cb)(argcount, argvars, rettv, fp->uf_cb_state);
|
||||||
} else if (fp != NULL) {
|
} else if (fp != NULL) {
|
||||||
if (argv_func != NULL) {
|
if (argv_func != NULL) {
|
||||||
// postponed filling in the arguments, do it now
|
// postponed filling in the arguments, do it now
|
||||||
@@ -3435,3 +3452,29 @@ bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID)
|
|||||||
xfree(tofree);
|
xfree(tofree);
|
||||||
return abort;
|
return abort;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Registers a C extension user 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;
|
||||||
|
|
||||||
|
fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
|
||||||
|
if (fp == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_cb = cb;
|
||||||
|
fp->uf_cb_free = cb_free;
|
||||||
|
fp->uf_cb_state = state;
|
||||||
|
|
||||||
|
STRCPY(fp->uf_name, name);
|
||||||
|
hash_add(&func_hashtab, UF2HIKEY(fp));
|
||||||
|
|
||||||
|
return fp->uf_name;
|
||||||
|
}
|
||||||
|
@@ -19,6 +19,7 @@
|
|||||||
#include "nvim/globals.h"
|
#include "nvim/globals.h"
|
||||||
#include "nvim/message.h"
|
#include "nvim/message.h"
|
||||||
#include "nvim/eval/typval.h"
|
#include "nvim/eval/typval.h"
|
||||||
|
#include "nvim/eval/userfunc.h"
|
||||||
#include "nvim/ascii.h"
|
#include "nvim/ascii.h"
|
||||||
#include "nvim/macros.h"
|
#include "nvim/macros.h"
|
||||||
|
|
||||||
@@ -50,6 +51,7 @@ typedef struct {
|
|||||||
#define LUA_PUSH_STATIC_STRING(lstate, s) \
|
#define LUA_PUSH_STATIC_STRING(lstate, s) \
|
||||||
lua_pushlstring(lstate, s, sizeof(s) - 1)
|
lua_pushlstring(lstate, s, sizeof(s) - 1)
|
||||||
|
|
||||||
|
|
||||||
static LuaTableProps nlua_traverse_table(lua_State *const lstate)
|
static LuaTableProps nlua_traverse_table(lua_State *const lstate)
|
||||||
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
|
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
|
||||||
{
|
{
|
||||||
@@ -314,6 +316,13 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LUA_TTABLE: {
|
case LUA_TTABLE: {
|
||||||
|
// Only need to track table refs if we have a metatable associated.
|
||||||
|
LuaRef table_ref = LUA_NOREF;
|
||||||
|
if (lua_getmetatable(lstate, -1)) {
|
||||||
|
lua_pop(lstate, 1);
|
||||||
|
table_ref = nlua_ref(lstate, -1);
|
||||||
|
}
|
||||||
|
|
||||||
const LuaTableProps table_props = nlua_traverse_table(lstate);
|
const LuaTableProps table_props = nlua_traverse_table(lstate);
|
||||||
|
|
||||||
for (size_t i = 0; i < kv_size(stack); i++) {
|
for (size_t i = 0; i < kv_size(stack); i++) {
|
||||||
@@ -329,6 +338,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
|
|||||||
case kObjectTypeArray: {
|
case kObjectTypeArray: {
|
||||||
cur.tv->v_type = VAR_LIST;
|
cur.tv->v_type = VAR_LIST;
|
||||||
cur.tv->vval.v_list = tv_list_alloc((ptrdiff_t)table_props.maxidx);
|
cur.tv->vval.v_list = tv_list_alloc((ptrdiff_t)table_props.maxidx);
|
||||||
|
cur.tv->vval.v_list->lua_table_ref = table_ref;
|
||||||
tv_list_ref(cur.tv->vval.v_list);
|
tv_list_ref(cur.tv->vval.v_list);
|
||||||
if (table_props.maxidx != 0) {
|
if (table_props.maxidx != 0) {
|
||||||
cur.container = true;
|
cur.container = true;
|
||||||
@@ -342,6 +352,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
|
|||||||
cur.tv->v_type = VAR_DICT;
|
cur.tv->v_type = VAR_DICT;
|
||||||
cur.tv->vval.v_dict = tv_dict_alloc();
|
cur.tv->vval.v_dict = tv_dict_alloc();
|
||||||
cur.tv->vval.v_dict->dv_refcount++;
|
cur.tv->vval.v_dict->dv_refcount++;
|
||||||
|
cur.tv->vval.v_dict->lua_table_ref = table_ref;
|
||||||
} else {
|
} else {
|
||||||
cur.special = table_props.has_string_with_nul;
|
cur.special = table_props.has_string_with_nul;
|
||||||
if (table_props.has_string_with_nul) {
|
if (table_props.has_string_with_nul) {
|
||||||
@@ -352,11 +363,13 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
|
|||||||
S_LEN("_VAL"));
|
S_LEN("_VAL"));
|
||||||
assert(val_di != NULL);
|
assert(val_di != NULL);
|
||||||
cur.tv = &val_di->di_tv;
|
cur.tv = &val_di->di_tv;
|
||||||
|
cur.tv->vval.v_list->lua_table_ref = table_ref;
|
||||||
assert(cur.tv->v_type == VAR_LIST);
|
assert(cur.tv->v_type == VAR_LIST);
|
||||||
} else {
|
} else {
|
||||||
cur.tv->v_type = VAR_DICT;
|
cur.tv->v_type = VAR_DICT;
|
||||||
cur.tv->vval.v_dict = tv_dict_alloc();
|
cur.tv->vval.v_dict = tv_dict_alloc();
|
||||||
cur.tv->vval.v_dict->dv_refcount++;
|
cur.tv->vval.v_dict->dv_refcount++;
|
||||||
|
cur.tv->vval.v_dict->lua_table_ref = table_ref;
|
||||||
}
|
}
|
||||||
cur.container = true;
|
cur.container = true;
|
||||||
cur.idx = lua_gettop(lstate);
|
cur.idx = lua_gettop(lstate);
|
||||||
@@ -384,6 +397,20 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
|
|||||||
nlua_pop_typval_table_processing_end:
|
nlua_pop_typval_table_processing_end:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case LUA_TFUNCTION: {
|
||||||
|
LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
|
||||||
|
state->lua_callable.func_ref = nlua_ref(lstate, -1);
|
||||||
|
state->lua_callable.table_ref = LUA_NOREF;
|
||||||
|
|
||||||
|
char_u *name = register_cfunc(
|
||||||
|
&nlua_CFunction_func_call,
|
||||||
|
&nlua_CFunction_func_free,
|
||||||
|
state);
|
||||||
|
|
||||||
|
cur.tv->v_type = VAR_FUNC;
|
||||||
|
cur.tv->vval.v_string = vim_strsave(name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case LUA_TUSERDATA: {
|
case LUA_TUSERDATA: {
|
||||||
nlua_pushref(lstate, nlua_nil_ref);
|
nlua_pushref(lstate, nlua_nil_ref);
|
||||||
bool is_nil = lua_rawequal(lstate, -2, -1);
|
bool is_nil = lua_rawequal(lstate, -2, -1);
|
||||||
|
@@ -9,6 +9,15 @@
|
|||||||
#include "nvim/func_attr.h"
|
#include "nvim/func_attr.h"
|
||||||
#include "nvim/eval.h"
|
#include "nvim/eval.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
LuaRef func_ref;
|
||||||
|
LuaRef table_ref;
|
||||||
|
} LuaCallable;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
LuaCallable lua_callable;
|
||||||
|
} LuaCFunctionState;
|
||||||
|
|
||||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
# include "lua/converter.h.generated.h"
|
# include "lua/converter.h.generated.h"
|
||||||
#endif
|
#endif
|
||||||
|
@@ -35,8 +35,8 @@
|
|||||||
#include "nvim/os/os.h"
|
#include "nvim/os/os.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "nvim/lua/executor.h"
|
|
||||||
#include "nvim/lua/converter.h"
|
#include "nvim/lua/converter.h"
|
||||||
|
#include "nvim/lua/executor.h"
|
||||||
#include "nvim/lua/treesitter.h"
|
#include "nvim/lua/treesitter.h"
|
||||||
|
|
||||||
#include "luv/luv.h"
|
#include "luv/luv.h"
|
||||||
@@ -53,6 +53,15 @@ typedef struct {
|
|||||||
# include "lua/executor.c.generated.h"
|
# include "lua/executor.c.generated.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define PUSH_ALL_TYPVALS(lstate, args, argcount, special) \
|
||||||
|
for (int i = 0; i < argcount; i++) { \
|
||||||
|
if (args[i].v_type == VAR_UNKNOWN) { \
|
||||||
|
lua_pushnil(lstate); \
|
||||||
|
} else { \
|
||||||
|
nlua_push_typval(lstate, &args[i], special); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert lua error into a Vim error message
|
/// Convert lua error into a Vim error message
|
||||||
///
|
///
|
||||||
/// @param lstate Lua interpreter state.
|
/// @param lstate Lua interpreter state.
|
||||||
@@ -700,24 +709,25 @@ int nlua_call(lua_State *lstate)
|
|||||||
}
|
}
|
||||||
|
|
||||||
TRY_WRAP({
|
TRY_WRAP({
|
||||||
// TODO(bfredl): this should be simplified in error handling refactor
|
// TODO(bfredl): this should be simplified in error handling refactor
|
||||||
force_abort = false;
|
force_abort = false;
|
||||||
suppress_errthrow = false;
|
suppress_errthrow = false;
|
||||||
current_exception = NULL;
|
current_exception = NULL;
|
||||||
did_emsg = false;
|
did_emsg = false;
|
||||||
|
|
||||||
try_start();
|
try_start();
|
||||||
typval_T rettv;
|
typval_T rettv;
|
||||||
int dummy;
|
int dummy;
|
||||||
// call_func() retval is deceptive, ignore it. Instead we set `msg_list`
|
// call_func() retval is deceptive, ignore it. Instead we set `msg_list`
|
||||||
// (TRY_WRAP) to capture abort-causing non-exception errors.
|
// (TRY_WRAP) to capture abort-causing non-exception errors.
|
||||||
(void)call_func(name, (int)name_len, &rettv, nargs,
|
(void)call_func(name, (int)name_len, &rettv, nargs,
|
||||||
vim_args, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum,
|
vim_args, NULL,
|
||||||
&dummy, true, NULL, NULL);
|
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
|
||||||
if (!try_end(&err)) {
|
&dummy, true, NULL, NULL);
|
||||||
nlua_push_typval(lstate, &rettv, false);
|
if (!try_end(&err)) {
|
||||||
}
|
nlua_push_typval(lstate, &rettv, false);
|
||||||
tv_clear(&rettv);
|
}
|
||||||
|
tv_clear(&rettv);
|
||||||
});
|
});
|
||||||
|
|
||||||
free_vim_args:
|
free_vim_args:
|
||||||
@@ -833,12 +843,25 @@ void executor_free_luaref(LuaRef ref)
|
|||||||
nlua_unref(lstate, ref);
|
nlua_unref(lstate, ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// push a value referenced in the regirstry
|
/// push a value referenced in the registry
|
||||||
void nlua_pushref(lua_State *lstate, LuaRef ref)
|
void nlua_pushref(lua_State *lstate, LuaRef ref)
|
||||||
{
|
{
|
||||||
lua_rawgeti(lstate, LUA_REGISTRYINDEX, ref);
|
lua_rawgeti(lstate, LUA_REGISTRYINDEX, ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets a new reference to an object stored at original_ref
|
||||||
|
///
|
||||||
|
/// NOTE: It does not copy the value, it creates a new ref to the lua object.
|
||||||
|
/// Leaves the stack unchanged.
|
||||||
|
LuaRef nlua_newref(lua_State *lstate, LuaRef original_ref)
|
||||||
|
{
|
||||||
|
nlua_pushref(lstate, original_ref);
|
||||||
|
LuaRef new_ref = nlua_ref(lstate, -1);
|
||||||
|
lua_pop(lstate, 1);
|
||||||
|
|
||||||
|
return new_ref;
|
||||||
|
}
|
||||||
|
|
||||||
/// Evaluate lua string
|
/// Evaluate lua string
|
||||||
///
|
///
|
||||||
/// Used for luaeval().
|
/// Used for luaeval().
|
||||||
@@ -916,13 +939,8 @@ static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < argcount; i++) {
|
PUSH_ALL_TYPVALS(lstate, args, argcount, special);
|
||||||
if (args[i].v_type == VAR_UNKNOWN) {
|
|
||||||
lua_pushnil(lstate);
|
|
||||||
} else {
|
|
||||||
nlua_push_typval(lstate, &args[i], special);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lua_pcall(lstate, argcount, ret_tv ? 1 : 0, 0)) {
|
if (lua_pcall(lstate, argcount, ret_tv ? 1 : 0, 0)) {
|
||||||
nlua_error(lstate, _("E5108: Error executing lua %.*s"));
|
nlua_error(lstate, _("E5108: Error executing lua %.*s"));
|
||||||
return;
|
return;
|
||||||
@@ -933,6 +951,51 @@ static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Call a LuaCallable given some typvals
|
||||||
|
///
|
||||||
|
/// Used to call any lua callable passed from Lua into VimL
|
||||||
|
///
|
||||||
|
/// @param[in] lstate Lua State
|
||||||
|
/// @param[in] lua_cb Lua Callable
|
||||||
|
/// @param[in] argcount Count of typval arguments
|
||||||
|
/// @param[in] argvars Typval Arguments
|
||||||
|
/// @param[out] rettv The return value from the called function.
|
||||||
|
int typval_exec_lua_callable(
|
||||||
|
lua_State *lstate,
|
||||||
|
LuaCallable lua_cb,
|
||||||
|
int argcount,
|
||||||
|
typval_T *argvars,
|
||||||
|
typval_T *rettv
|
||||||
|
)
|
||||||
|
{
|
||||||
|
int offset = 0;
|
||||||
|
LuaRef cb = lua_cb.func_ref;
|
||||||
|
|
||||||
|
if (cb == LUA_NOREF) {
|
||||||
|
// This shouldn't happen.
|
||||||
|
luaL_error(lstate, "Invalid function passed to VimL");
|
||||||
|
return ERROR_OTHER;
|
||||||
|
}
|
||||||
|
|
||||||
|
nlua_pushref(lstate, cb);
|
||||||
|
|
||||||
|
if (lua_cb.table_ref != LUA_NOREF) {
|
||||||
|
offset += 1;
|
||||||
|
nlua_pushref(lstate, lua_cb.table_ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
PUSH_ALL_TYPVALS(lstate, argvars, argcount, false);
|
||||||
|
|
||||||
|
if (lua_pcall(lstate, argcount + offset, 1, 0)) {
|
||||||
|
luaL_error(lstate, "nlua_CFunction_func_call failed.");
|
||||||
|
return ERROR_OTHER;
|
||||||
|
}
|
||||||
|
|
||||||
|
nlua_pop_typval(lstate, rettv);
|
||||||
|
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
/// Execute Lua string
|
/// Execute Lua string
|
||||||
///
|
///
|
||||||
/// Used for nvim_exec_lua().
|
/// Used for nvim_exec_lua().
|
||||||
@@ -1280,3 +1343,115 @@ static int regex_match_line(lua_State *lstate)
|
|||||||
|
|
||||||
return nret;
|
return nret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int nlua_CFunction_func_call(
|
||||||
|
int argcount,
|
||||||
|
typval_T *argvars,
|
||||||
|
typval_T *rettv,
|
||||||
|
void *state)
|
||||||
|
{
|
||||||
|
lua_State *const lstate = nlua_enter();
|
||||||
|
LuaCFunctionState *funcstate = (LuaCFunctionState *)state;
|
||||||
|
|
||||||
|
return typval_exec_lua_callable(
|
||||||
|
lstate,
|
||||||
|
funcstate->lua_callable,
|
||||||
|
argcount,
|
||||||
|
argvars,
|
||||||
|
rettv);
|
||||||
|
}
|
||||||
|
/// Required functions for lua c functions as VimL callbacks
|
||||||
|
void nlua_CFunction_func_free(void *state)
|
||||||
|
{
|
||||||
|
lua_State *const lstate = nlua_enter();
|
||||||
|
LuaCFunctionState *funcstate = (LuaCFunctionState *)state;
|
||||||
|
|
||||||
|
nlua_unref(lstate, funcstate->lua_callable.func_ref);
|
||||||
|
nlua_unref(lstate, funcstate->lua_callable.table_ref);
|
||||||
|
xfree(funcstate);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nlua_is_table_from_lua(typval_T *const arg)
|
||||||
|
{
|
||||||
|
if (arg->v_type != VAR_DICT && arg->v_type != VAR_LIST) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg->v_type == VAR_DICT) {
|
||||||
|
return arg->vval.v_dict->lua_table_ref > 0
|
||||||
|
&& arg->vval.v_dict->lua_table_ref != LUA_NOREF;
|
||||||
|
} else if (arg->v_type == VAR_LIST) {
|
||||||
|
return arg->vval.v_list->lua_table_ref > 0
|
||||||
|
&& arg->vval.v_list->lua_table_ref != LUA_NOREF;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char_u *nlua_register_table_as_callable(typval_T *const arg)
|
||||||
|
{
|
||||||
|
if (!nlua_is_table_from_lua(arg)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
LuaRef table_ref;
|
||||||
|
if (arg->v_type == VAR_DICT) {
|
||||||
|
table_ref = arg->vval.v_dict->lua_table_ref;
|
||||||
|
} else if (arg->v_type == VAR_LIST) {
|
||||||
|
table_ref = arg->vval.v_list->lua_table_ref;
|
||||||
|
} else {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_State *const lstate = nlua_enter();
|
||||||
|
|
||||||
|
int top = lua_gettop(lstate);
|
||||||
|
|
||||||
|
nlua_pushref(lstate, table_ref);
|
||||||
|
if (!lua_getmetatable(lstate, -1)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_getfield(lstate, -1, "__call");
|
||||||
|
if (!lua_isfunction(lstate, -1)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
LuaRef new_table_ref = nlua_newref(lstate, table_ref);
|
||||||
|
|
||||||
|
LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
|
||||||
|
state->lua_callable.func_ref = nlua_ref(lstate, -1);
|
||||||
|
state->lua_callable.table_ref = new_table_ref;
|
||||||
|
|
||||||
|
char_u *name = register_cfunc(
|
||||||
|
&nlua_CFunction_func_call,
|
||||||
|
&nlua_CFunction_func_free,
|
||||||
|
state);
|
||||||
|
|
||||||
|
|
||||||
|
lua_pop(lstate, 3);
|
||||||
|
assert(top == lua_gettop(lstate));
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to free a list_T
|
||||||
|
void nlua_free_typval_list(list_T *const l)
|
||||||
|
{
|
||||||
|
if (l->lua_table_ref != LUA_NOREF && l->lua_table_ref > 0) {
|
||||||
|
lua_State *const lstate = nlua_enter();
|
||||||
|
nlua_unref(lstate, l->lua_table_ref);
|
||||||
|
l->lua_table_ref = LUA_NOREF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Helper function to free a dict_T
|
||||||
|
void nlua_free_typval_dict(dict_T *const d)
|
||||||
|
{
|
||||||
|
if (d->lua_table_ref != LUA_NOREF && d->lua_table_ref > 0) {
|
||||||
|
lua_State *const lstate = nlua_enter();
|
||||||
|
nlua_unref(lstate, d->lua_table_ref);
|
||||||
|
d->lua_table_ref = LUA_NOREF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
#include "nvim/func_attr.h"
|
#include "nvim/func_attr.h"
|
||||||
#include "nvim/eval/typval.h"
|
#include "nvim/eval/typval.h"
|
||||||
#include "nvim/ex_cmds_defs.h"
|
#include "nvim/ex_cmds_defs.h"
|
||||||
|
#include "nvim/lua/converter.h"
|
||||||
|
|
||||||
// Generated by msgpack-gen.lua
|
// Generated by msgpack-gen.lua
|
||||||
void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL;
|
void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL;
|
||||||
|
@@ -272,6 +272,10 @@ vim.fn = setmetatable({}, {
|
|||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
|
vim.funcref = function(viml_func_name)
|
||||||
|
return vim.fn[viml_func_name]
|
||||||
|
end
|
||||||
|
|
||||||
-- These are for loading runtime modules lazily since they aren't available in
|
-- These are for loading runtime modules lazily since they aren't available in
|
||||||
-- the nvim binary as specified in executor.c
|
-- the nvim binary as specified in executor.c
|
||||||
local function __index(t, key)
|
local function __index(t, key)
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
local helpers = require('test.functional.helpers')(after_each)
|
local helpers = require('test.functional.helpers')(after_each)
|
||||||
local Screen = require('test.functional.ui.screen')
|
local Screen = require('test.functional.ui.screen')
|
||||||
|
|
||||||
local redir_exec = helpers.redir_exec
|
|
||||||
local pcall_err = helpers.pcall_err
|
local pcall_err = helpers.pcall_err
|
||||||
local exc_exec = helpers.exc_exec
|
local exc_exec = helpers.exc_exec
|
||||||
local exec_lua = helpers.exec_lua
|
local exec_lua = helpers.exec_lua
|
||||||
@@ -188,23 +187,198 @@ describe('luaeval()', function()
|
|||||||
it('issues an error in some cases', function()
|
it('issues an error in some cases', function()
|
||||||
eq("Vim(call):E5100: Cannot convert given lua table: table should either have a sequence of positive integer keys or contain only string keys",
|
eq("Vim(call):E5100: Cannot convert given lua table: table should either have a sequence of positive integer keys or contain only string keys",
|
||||||
exc_exec('call luaeval("{1, foo=2}")'))
|
exc_exec('call luaeval("{1, foo=2}")'))
|
||||||
eq("Vim(call):E5101: Cannot convert given lua type",
|
|
||||||
exc_exec('call luaeval("vim.api.nvim_buf_get_lines")'))
|
|
||||||
startswith("Vim(call):E5107: Error loading lua [string \"luaeval()\"]:",
|
startswith("Vim(call):E5107: Error loading lua [string \"luaeval()\"]:",
|
||||||
exc_exec('call luaeval("1, 2, 3")'))
|
exc_exec('call luaeval("1, 2, 3")'))
|
||||||
startswith("Vim(call):E5108: Error executing lua [string \"luaeval()\"]:",
|
startswith("Vim(call):E5108: Error executing lua [string \"luaeval()\"]:",
|
||||||
exc_exec('call luaeval("(nil)()")'))
|
exc_exec('call luaeval("(nil)()")'))
|
||||||
eq("Vim(call):E5101: Cannot convert given lua type",
|
|
||||||
exc_exec('call luaeval("{42, vim.api}")'))
|
|
||||||
eq("Vim(call):E5101: Cannot convert given lua type",
|
|
||||||
exc_exec('call luaeval("{foo=42, baz=vim.api}")'))
|
|
||||||
|
|
||||||
-- The following should not crash: conversion error happens inside
|
end)
|
||||||
eq("Vim(call):E5101: Cannot convert given lua type",
|
|
||||||
exc_exec('call luaeval("vim.api")'))
|
it('should handle sending lua functions to viml', function()
|
||||||
-- The following should not show internal error
|
eq(true, exec_lua [[
|
||||||
eq("\nE5101: Cannot convert given lua type\n0",
|
can_pass_lua_callback_to_vim_from_lua_result = nil
|
||||||
redir_exec('echo luaeval("vim.api")'))
|
|
||||||
|
vim.fn.call(function()
|
||||||
|
can_pass_lua_callback_to_vim_from_lua_result = true
|
||||||
|
end, {})
|
||||||
|
|
||||||
|
return can_pass_lua_callback_to_vim_from_lua_result
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('run functions even in timers', function()
|
||||||
|
eq(true, exec_lua [[
|
||||||
|
can_pass_lua_callback_to_vim_from_lua_result = nil
|
||||||
|
|
||||||
|
vim.fn.timer_start(50, function()
|
||||||
|
can_pass_lua_callback_to_vim_from_lua_result = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
vim.wait(1000, function()
|
||||||
|
return can_pass_lua_callback_to_vim_from_lua_result
|
||||||
|
end)
|
||||||
|
|
||||||
|
return can_pass_lua_callback_to_vim_from_lua_result
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('can run named functions more than once', function()
|
||||||
|
eq(5, exec_lua [[
|
||||||
|
count_of_vals = 0
|
||||||
|
|
||||||
|
vim.fn.timer_start(5, function()
|
||||||
|
count_of_vals = count_of_vals + 1
|
||||||
|
end, {['repeat'] = 5})
|
||||||
|
|
||||||
|
vim.fn.wait(1000, function()
|
||||||
|
return count_of_vals >= 5
|
||||||
|
end)
|
||||||
|
|
||||||
|
return count_of_vals
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('can handle clashing names', function()
|
||||||
|
eq(1, exec_lua [[
|
||||||
|
local f_loc = function() return 1 end
|
||||||
|
|
||||||
|
local result = nil
|
||||||
|
vim.fn.timer_start(100, function()
|
||||||
|
result = f_loc()
|
||||||
|
end)
|
||||||
|
|
||||||
|
local f_loc = function() return 2 end
|
||||||
|
vim.wait(1000, function() return result ~= nil end)
|
||||||
|
|
||||||
|
return result
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('should handle passing functions around', function()
|
||||||
|
command [[
|
||||||
|
function VimCanCallLuaCallbacks(Concat, Cb)
|
||||||
|
let message = a:Concat("Hello Vim", "I'm Lua")
|
||||||
|
call a:Cb(message)
|
||||||
|
endfunction
|
||||||
|
]]
|
||||||
|
|
||||||
|
eq("Hello Vim I'm Lua", exec_lua [[
|
||||||
|
can_pass_lua_callback_to_vim_from_lua_result = ""
|
||||||
|
|
||||||
|
vim.fn.VimCanCallLuaCallbacks(
|
||||||
|
function(greeting, message) return greeting .. " " .. message end,
|
||||||
|
function(message) can_pass_lua_callback_to_vim_from_lua_result = message end
|
||||||
|
)
|
||||||
|
|
||||||
|
return can_pass_lua_callback_to_vim_from_lua_result
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('should handle funcrefs', function()
|
||||||
|
command [[
|
||||||
|
function VimCanCallLuaCallbacks(Concat, Cb)
|
||||||
|
let message = a:Concat("Hello Vim", "I'm Lua")
|
||||||
|
call a:Cb(message)
|
||||||
|
endfunction
|
||||||
|
]]
|
||||||
|
|
||||||
|
eq("Hello Vim I'm Lua", exec_lua [[
|
||||||
|
can_pass_lua_callback_to_vim_from_lua_result = ""
|
||||||
|
|
||||||
|
vim.funcref('VimCanCallLuaCallbacks')(
|
||||||
|
function(greeting, message) return greeting .. " " .. message end,
|
||||||
|
function(message) can_pass_lua_callback_to_vim_from_lua_result = message end
|
||||||
|
)
|
||||||
|
|
||||||
|
return can_pass_lua_callback_to_vim_from_lua_result
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('should work with metatables using __call', function()
|
||||||
|
eq(1, exec_lua [[
|
||||||
|
local this_is_local_variable = false
|
||||||
|
local callable_table = setmetatable({x = 1}, {
|
||||||
|
__call = function(t, ...)
|
||||||
|
this_is_local_variable = t.x
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
vim.fn.timer_start(5, callable_table)
|
||||||
|
|
||||||
|
vim.wait(1000, function()
|
||||||
|
return this_is_local_variable
|
||||||
|
end)
|
||||||
|
|
||||||
|
return this_is_local_variable
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('should handle being called from a timer once.', function()
|
||||||
|
eq(3, exec_lua [[
|
||||||
|
local this_is_local_variable = false
|
||||||
|
local callable_table = setmetatable({5, 4, 3, 2, 1}, {
|
||||||
|
__call = function(t, ...) this_is_local_variable = t[3] end
|
||||||
|
})
|
||||||
|
|
||||||
|
vim.fn.timer_start(5, callable_table)
|
||||||
|
vim.wait(1000, function()
|
||||||
|
return this_is_local_variable
|
||||||
|
end)
|
||||||
|
|
||||||
|
return this_is_local_variable
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('should call functions once with __call metamethod', function()
|
||||||
|
eq(true, exec_lua [[
|
||||||
|
local this_is_local_variable = false
|
||||||
|
local callable_table = setmetatable({a = true, b = false}, {
|
||||||
|
__call = function(t, ...) this_is_local_variable = t.a end
|
||||||
|
})
|
||||||
|
|
||||||
|
assert(getmetatable(callable_table).__call)
|
||||||
|
vim.fn.call(callable_table, {})
|
||||||
|
|
||||||
|
return this_is_local_variable
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('should work with lists using __call', function()
|
||||||
|
eq(3, exec_lua [[
|
||||||
|
local this_is_local_variable = false
|
||||||
|
local mt = {
|
||||||
|
__call = function(t, ...)
|
||||||
|
this_is_local_variable = t[3]
|
||||||
|
end
|
||||||
|
}
|
||||||
|
local callable_table = setmetatable({5, 4, 3, 2, 1}, mt)
|
||||||
|
|
||||||
|
-- Call it once...
|
||||||
|
vim.fn.timer_start(5, callable_table)
|
||||||
|
vim.wait(1000, function()
|
||||||
|
return this_is_local_variable
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert(this_is_local_variable)
|
||||||
|
this_is_local_variable = false
|
||||||
|
|
||||||
|
vim.fn.timer_start(5, callable_table)
|
||||||
|
vim.wait(1000, function()
|
||||||
|
return this_is_local_variable
|
||||||
|
end)
|
||||||
|
|
||||||
|
return this_is_local_variable
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('should not work with tables not using __call', function()
|
||||||
|
eq({false, 'Vim:E921: Invalid callback argument'}, exec_lua [[
|
||||||
|
local this_is_local_variable = false
|
||||||
|
local callable_table = setmetatable({x = 1}, {})
|
||||||
|
|
||||||
|
return {pcall(function() vim.fn.timer_start(5, callable_table) end)}
|
||||||
|
]])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('correctly converts containers with type_idx', function()
|
it('correctly converts containers with type_idx', function()
|
||||||
|
Reference in New Issue
Block a user