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 > | is equivalent to the Lua chunk > | ||||||
|     return somemod.func(...) |     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. | You can use `v:lua` in "func" options like 'tagfunc', 'omnifunc', etc. | ||||||
| For example consider the following Lua omnifunc handler: > | 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; |   return ret; | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Evaluate "->method()". | /// Evaluate "->method()" or "->v:lua.method()". | ||||||
| /// @note "*arg" points to the '-'. | /// @note "*arg" points to the '-'. | ||||||
| /// @return FAIL or OK. "*arg" is advanced to after the ')'. | /// @return FAIL or OK. "*arg" is advanced to after the ')'. | ||||||
| static int eval_method(char_u **const arg, typval_T *const rettv, | 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; |   rettv->v_type = VAR_UNKNOWN; | ||||||
|  |  | ||||||
|   // Locate the method name. |   // Locate the method name. | ||||||
|  |   int len; | ||||||
|   char_u *name = *arg; |   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; |     char_u *alias; | ||||||
|  |     len = get_name_len((const char **)arg, (char **)&alias, evaluate, true); | ||||||
|   const int len |  | ||||||
|       = get_name_len((const char **)arg, (char **)&alias, evaluate, true); |  | ||||||
|     if (alias != NULL) { |     if (alias != NULL) { | ||||||
|       name = alias; |       name = alias; | ||||||
|     } |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   int ret; |   int ret; | ||||||
|   if (len <= 0) { |   if (len <= 0) { | ||||||
|     if (verbose) { |     if (verbose) { | ||||||
|  |       if (lua_funcname == NULL) { | ||||||
|         EMSG(_("E260: Missing name after ->")); |         EMSG(_("E260: Missing name after ->")); | ||||||
|  |       } else { | ||||||
|  |         EMSG2(_(e_invexpr2), name); | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|     ret = FAIL; |     ret = FAIL; | ||||||
|   } else { |   } else { | ||||||
| @@ -4251,6 +4262,13 @@ static int eval_method(char_u **const arg, typval_T *const rettv, | |||||||
|         EMSG(_(e_nowhitespace)); |         EMSG(_(e_nowhitespace)); | ||||||
|       } |       } | ||||||
|       ret = FAIL; |       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 { |     } else { | ||||||
|       ret = eval_func(arg, name, len, rettv, evaluate, &base); |       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); |   return tv->v_type == VAR_PARTIAL && is_luafunc(tv->vval.v_partial); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// check the function name after "v:lua." | /// Skips one character past the end of the name of a v:lua function. | ||||||
| int check_luafunc_name(const char *str, bool paren) | /// @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 == '\'') { |   while (ASCII_ISALNUM(*p) || *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)) { |   if (*p != (paren ? '(' : NUL)) { | ||||||
|     return 0; |     return 0; | ||||||
|   } else { |   } 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 | /// Call a function with its resolved parameters | ||||||
| /// | /// | ||||||
| /// @return FAIL if function cannot be called, else OK (even if an error | /// @return FAIL if function cannot be called, else OK (even if an error | ||||||
| @@ -1515,6 +1532,7 @@ call_func( | |||||||
|     if (is_luafunc(partial)) { |     if (is_luafunc(partial)) { | ||||||
|       if (len > 0) { |       if (len > 0) { | ||||||
|         error = ERROR_NONE; |         error = ERROR_NONE; | ||||||
|  |         argv_add_base(funcexe->basetv, &argvars, &argcount, argv, &argv_base); | ||||||
|         nlua_typval_call((const char *)funcname, len, argvars, argcount, rettv); |         nlua_typval_call((const char *)funcname, len, argvars, argcount, rettv); | ||||||
|       } else { |       } else { | ||||||
|         // v:lua was called directly; show its name in the emsg |         // v:lua was called directly; show its name in the emsg | ||||||
| @@ -1553,14 +1571,7 @@ call_func( | |||||||
|                                         fp->uf_args.ga_len); |                                         fp->uf_args.ga_len); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (funcexe->basetv != NULL) { |         argv_add_base(funcexe->basetv, &argvars, &argcount, argv, &argv_base); | ||||||
|           // Method call: base->Method() |  | ||||||
|           memmove(&argv[1], argvars, sizeof(typval_T) * argcount); |  | ||||||
|           argv[0] = *funcexe->basetv; |  | ||||||
|           argcount++; |  | ||||||
|           argvars = argv; |  | ||||||
|           argv_base = 1; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (fp->uf_flags & FC_RANGE && funcexe->doesrange != NULL) { |         if (fp->uf_flags & FC_RANGE && funcexe->doesrange != NULL) { | ||||||
|           *funcexe->doesrange = true; |           *funcexe->doesrange = true; | ||||||
|   | |||||||
| @@ -481,6 +481,21 @@ describe('v:lua', function() | |||||||
|        pcall_err(eval, 'v:lua.mymod.crashy()')) |        pcall_err(eval, 'v:lua.mymod.crashy()')) | ||||||
|   end) |   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() |   it('works in :call', function() | ||||||
|     command(":call v:lua.mymod.noisy('command')") |     command(":call v:lua.mymod.noisy('command')") | ||||||
|     eq("hey command", meths.get_current_line()) |     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(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) | ||||||
| end) | end) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Sean Dewar
					Sean Dewar