aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Dewar <seandewar@users.noreply.github.com>2021-08-11 13:47:33 +0100
committerSean Dewar <seandewar@users.noreply.github.com>2021-08-13 01:11:36 +0100
commitb2994e35c9357a8144beaf27e1a8ea4dd133f5d4 (patch)
tree8c8f5b17fc75ae51c55aae8d247462e49426678c
parentda9005af792f7a7eaae98ee9f6499af9a97fd095 (diff)
downloadrneovim-b2994e35c9357a8144beaf27e1a8ea4dd133f5d4.tar.gz
rneovim-b2994e35c9357a8144beaf27e1a8ea4dd133f5d4.tar.bz2
rneovim-b2994e35c9357a8144beaf27e1a8ea4dd133f5d4.zip
feat(v:lua): support calling v:lua as a method
-rw-r--r--runtime/doc/lua.txt4
-rw-r--r--src/nvim/eval.c50
-rw-r--r--src/nvim/eval/userfunc.c27
-rw-r--r--test/functional/lua/luaeval_spec.lua21
4 files changed, 83 insertions, 19 deletions
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index fd1bedd8ef..1bbfde1980 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -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: >
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index a6becb4765..472b43a387 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -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 *alias;
-
- const int len
- = get_name_len((const char **)arg, (char **)&alias, evaluate, true);
- if (alias != NULL) {
- name = alias;
+ 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;
+ len = get_name_len((const char **)arg, (char **)&alias, evaluate, true);
+ if (alias != NULL) {
+ name = alias;
+ }
}
int ret;
if (len <= 0) {
if (verbose) {
- EMSG(_("E260: Missing name after ->"));
+ 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 {
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index 5ffd578891..4184e4d922 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -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;
diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua
index 263408ad33..8ef77faa0f 100644
--- a/test/functional/lua/luaeval_spec.lua
+++ b/test/functional/lua/luaeval_spec.lua
@@ -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)