mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	feat(v:lua): support calling v:lua as a method
This commit is contained in:
		| @@ -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 *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 { | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Sean Dewar
					Sean Dewar