mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 01:34:25 +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 *alias;
 | 
					  char_u *lua_funcname = NULL;
 | 
				
			||||||
 | 
					  if (STRNCMP(name, "v:lua.", 6) == 0) {
 | 
				
			||||||
  const int len
 | 
					    lua_funcname = name + 6;
 | 
				
			||||||
      = get_name_len((const char **)arg, (char **)&alias, evaluate, true);
 | 
					    *arg = (char_u *)skip_luafunc_name((const char *)lua_funcname);
 | 
				
			||||||
  if (alias != NULL) {
 | 
					    *arg = skipwhite(*arg);  // to detect trailing whitespace later
 | 
				
			||||||
    name = alias;
 | 
					    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;
 | 
					  int ret;
 | 
				
			||||||
  if (len <= 0) {
 | 
					  if (len <= 0) {
 | 
				
			||||||
    if (verbose) {
 | 
					    if (verbose) {
 | 
				
			||||||
      EMSG(_("E260: Missing name after ->"));
 | 
					      if (lua_funcname == NULL) {
 | 
				
			||||||
 | 
					        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