diff options
author | Sean Dewar <seandewar@users.noreply.github.com> | 2021-08-11 13:47:33 +0100 |
---|---|---|
committer | Sean Dewar <seandewar@users.noreply.github.com> | 2021-08-13 01:11:36 +0100 |
commit | b2994e35c9357a8144beaf27e1a8ea4dd133f5d4 (patch) | |
tree | 8c8f5b17fc75ae51c55aae8d247462e49426678c | |
parent | da9005af792f7a7eaae98ee9f6499af9a97fd095 (diff) | |
download | rneovim-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.txt | 4 | ||||
-rw-r--r-- | src/nvim/eval.c | 50 | ||||
-rw-r--r-- | src/nvim/eval/userfunc.c | 27 | ||||
-rw-r--r-- | test/functional/lua/luaeval_spec.lua | 21 |
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) |