mirror of
https://github.com/neovim/neovim.git
synced 2024-12-23 20:55:18 -07:00
feat(v:lua): support calling v:lua as a method
This commit is contained in:
parent
da9005af79
commit
b2994e35c9
@ -391,6 +391,10 @@ where the args are converted to Lua values. The expression >
|
||||
is equivalent to the Lua chunk >
|
||||
return somemod.func(...)
|
||||
|
||||
The `v:lua` prefix may be used to call Lua functions as |method|s. For
|
||||
example: >
|
||||
arg1->v:lua.somemod.func(arg2)
|
||||
|
||||
You can use `v:lua` in "func" options like 'tagfunc', 'omnifunc', etc.
|
||||
For example consider the following Lua omnifunc handler: >
|
||||
|
||||
|
@ -4212,7 +4212,7 @@ static int eval_lambda(char_u **const arg, typval_T *const rettv,
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Evaluate "->method()".
|
||||
/// Evaluate "->method()" or "->v:lua.method()".
|
||||
/// @note "*arg" points to the '-'.
|
||||
/// @return FAIL or OK. "*arg" is advanced to after the ')'.
|
||||
static int eval_method(char_u **const arg, typval_T *const rettv,
|
||||
@ -4225,19 +4225,30 @@ static int eval_method(char_u **const arg, typval_T *const rettv,
|
||||
rettv->v_type = VAR_UNKNOWN;
|
||||
|
||||
// Locate the method name.
|
||||
int len;
|
||||
char_u *name = *arg;
|
||||
char_u *lua_funcname = NULL;
|
||||
if (STRNCMP(name, "v:lua.", 6) == 0) {
|
||||
lua_funcname = name + 6;
|
||||
*arg = (char_u *)skip_luafunc_name((const char *)lua_funcname);
|
||||
*arg = skipwhite(*arg); // to detect trailing whitespace later
|
||||
len = *arg - lua_funcname;
|
||||
} else {
|
||||
char_u *alias;
|
||||
|
||||
const int len
|
||||
= get_name_len((const char **)arg, (char **)&alias, evaluate, true);
|
||||
len = get_name_len((const char **)arg, (char **)&alias, evaluate, true);
|
||||
if (alias != NULL) {
|
||||
name = alias;
|
||||
}
|
||||
}
|
||||
|
||||
int ret;
|
||||
if (len <= 0) {
|
||||
if (verbose) {
|
||||
if (lua_funcname == NULL) {
|
||||
EMSG(_("E260: Missing name after ->"));
|
||||
} else {
|
||||
EMSG2(_(e_invexpr2), name);
|
||||
}
|
||||
}
|
||||
ret = FAIL;
|
||||
} else {
|
||||
@ -4251,6 +4262,13 @@ static int eval_method(char_u **const arg, typval_T *const rettv,
|
||||
EMSG(_(e_nowhitespace));
|
||||
}
|
||||
ret = FAIL;
|
||||
} else if (lua_funcname != NULL) {
|
||||
if (evaluate) {
|
||||
rettv->v_type = VAR_PARTIAL;
|
||||
rettv->vval.v_partial = vvlua_partial;
|
||||
rettv->vval.v_partial->pt_refcount++;
|
||||
}
|
||||
ret = call_func_rettv(arg, rettv, evaluate, NULL, &base, lua_funcname);
|
||||
} else {
|
||||
ret = eval_func(arg, name, len, rettv, evaluate, &base);
|
||||
}
|
||||
@ -8591,13 +8609,23 @@ static bool tv_is_luafunc(typval_T *tv)
|
||||
return tv->v_type == VAR_PARTIAL && is_luafunc(tv->vval.v_partial);
|
||||
}
|
||||
|
||||
/// check the function name after "v:lua."
|
||||
int check_luafunc_name(const char *str, bool paren)
|
||||
/// Skips one character past the end of the name of a v:lua function.
|
||||
/// @param p Pointer to the char AFTER the "v:lua." prefix.
|
||||
/// @return Pointer to the char one past the end of the function's name.
|
||||
const char *skip_luafunc_name(const char *p)
|
||||
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
const char *p = str;
|
||||
while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.' || *p == '\'') {
|
||||
p++;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/// check the function name after "v:lua."
|
||||
int check_luafunc_name(const char *const str, const bool paren)
|
||||
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
const char *const p = skip_luafunc_name(str);
|
||||
if (*p != (paren ? '(' : NUL)) {
|
||||
return 0;
|
||||
} else {
|
||||
|
@ -1423,6 +1423,23 @@ static void user_func_error(int error, const char_u *name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Used by call_func to add a method base (if any) to a function argument list
|
||||
/// as the first argument. @see call_func
|
||||
static void argv_add_base(typval_T *const basetv, typval_T **const argvars,
|
||||
int *const argcount, typval_T *const new_argvars,
|
||||
int *const argv_base)
|
||||
FUNC_ATTR_NONNULL_ARG(2, 3, 4, 5)
|
||||
{
|
||||
if (basetv != NULL) {
|
||||
// Method call: base->Method()
|
||||
memmove(&new_argvars[1], *argvars, sizeof(typval_T) * (*argcount));
|
||||
new_argvars[0] = *basetv;
|
||||
(*argcount)++;
|
||||
*argvars = new_argvars;
|
||||
*argv_base = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Call a function with its resolved parameters
|
||||
///
|
||||
/// @return FAIL if function cannot be called, else OK (even if an error
|
||||
@ -1515,6 +1532,7 @@ call_func(
|
||||
if (is_luafunc(partial)) {
|
||||
if (len > 0) {
|
||||
error = ERROR_NONE;
|
||||
argv_add_base(funcexe->basetv, &argvars, &argcount, argv, &argv_base);
|
||||
nlua_typval_call((const char *)funcname, len, argvars, argcount, rettv);
|
||||
} else {
|
||||
// v:lua was called directly; show its name in the emsg
|
||||
@ -1553,14 +1571,7 @@ call_func(
|
||||
fp->uf_args.ga_len);
|
||||
}
|
||||
|
||||
if (funcexe->basetv != NULL) {
|
||||
// Method call: base->Method()
|
||||
memmove(&argv[1], argvars, sizeof(typval_T) * argcount);
|
||||
argv[0] = *funcexe->basetv;
|
||||
argcount++;
|
||||
argvars = argv;
|
||||
argv_base = 1;
|
||||
}
|
||||
argv_add_base(funcexe->basetv, &argvars, &argcount, argv, &argv_base);
|
||||
|
||||
if (fp->uf_flags & FC_RANGE && funcexe->doesrange != NULL) {
|
||||
*funcexe->doesrange = true;
|
||||
|
@ -481,6 +481,21 @@ describe('v:lua', function()
|
||||
pcall_err(eval, 'v:lua.mymod.crashy()'))
|
||||
end)
|
||||
|
||||
it('works when called as a method', function()
|
||||
eq(123, eval('110->v:lua.foo(13)'))
|
||||
eq(true, exec_lua([[return _G.val == nil]]))
|
||||
|
||||
eq(321, eval('300->v:lua.foo(21, "boop")'))
|
||||
eq("boop", exec_lua([[return _G.val]]))
|
||||
|
||||
eq(NIL, eval('"there"->v:lua.mymod.noisy()'))
|
||||
eq("hey there", meths.get_current_line())
|
||||
eq({5, 10, 15, 20}, eval('[[1], [2, 3], [4]]->v:lua.vim.tbl_flatten()->map({_, v -> v * 5})'))
|
||||
|
||||
eq("Vim:E5108: Error executing lua [string \"<nvim>\"]:0: attempt to call global 'nonexistent' (a nil value)",
|
||||
pcall_err(eval, '"huh?"->v:lua.mymod.crashy()'))
|
||||
end)
|
||||
|
||||
it('works in :call', function()
|
||||
command(":call v:lua.mymod.noisy('command')")
|
||||
eq("hey command", meths.get_current_line())
|
||||
@ -522,5 +537,11 @@ describe('v:lua', function()
|
||||
|
||||
eq("Vim(let):E46: Cannot change read-only variable \"v:['lua']\"", pcall_err(command, "let v:['lua'] = 'xx'"))
|
||||
eq("Vim(let):E46: Cannot change read-only variable \"v:lua\"", pcall_err(command, "let v:lua = 'xx'"))
|
||||
|
||||
eq("Vim:E107: Missing parentheses: v:lua.func", pcall_err(eval, "'bad'->v:lua.func"))
|
||||
eq("Vim:E274: No white space allowed before parenthesis", pcall_err(eval, "'bad'->v:lua.func ()"))
|
||||
eq("Vim:E107: Missing parentheses: v:lua", pcall_err(eval, "'bad'->v:lua"))
|
||||
eq("Vim:E117: Unknown function: v:lua", pcall_err(eval, "'bad'->v:lua()"))
|
||||
eq("Vim:E15: Invalid expression: v:lua.()", pcall_err(eval, "'bad'->v:lua.()"))
|
||||
end)
|
||||
end)
|
||||
|
Loading…
Reference in New Issue
Block a user