diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 1eb873a5b4..79bf81dc0e 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1737,6 +1737,10 @@ v:lnum Line number for the 'foldexpr' |fold-expr|, 'formatexpr' and expressions is being evaluated. Read-only when in the |sandbox|. + *v:lua* *lua-variable* +v:lua Prefix for calling lua functions from expressions. + See |v:lua-call| for more information. + *v:mouse_win* *mouse_win-variable* v:mouse_win Window number for a mouse click obtained with |getchar()|. First window has number 1, like with |winnr()|. The value is diff --git a/runtime/doc/if_lua.txt b/runtime/doc/if_lua.txt index 7eef5d3903..911197acd4 100644 --- a/runtime/doc/if_lua.txt +++ b/runtime/doc/if_lua.txt @@ -326,6 +326,38 @@ Note: second argument to `luaeval` undergoes VimL to Lua conversion Return value is also always converted. When converting, |msgpack-special-dict|s are treated specially. +============================================================================== +v:lua function calls *v:lua-call* + +The special prefix `v:lua` can be used in vimL expressions to call lua +functions which are global or nested inside global tables. The expression +`v:lua.func(arg1, arg2)` is equivalent to executing the lua code +`return func(...)` where the args have been converted to lua values. In addition +`v:lua.somemod.func(args)` will work like `return somemod.func(...)` . + +`v:lua` can also be used in function options like 'omnifunc'. As an +example, consider the following lua implementation of an omnifunc: > + + function mymod.omnifunc(findstart, base) + if findstart == 1 then + return 0 + else + return {'stuff', 'steam', 'strange things'} + end + end + vim.api.nvim_buf_set_option(0, 'omnifunc', 'v:lua.mymod.omnifunc') + +A limitation is that the plugin module ("mymod" in this case) must +be made available as a global. + +Note: `v:lua` without a call is not allowed in a vimL expression. Funcrefs +to lua functions cannot be created. The following are errors: > + + let g:Myvar = v:lua.myfunc + call SomeFunc(v:lua.mycallback) + let g:foo = v:lua + let g:foo = v:['lua'] + ============================================================================== Lua standard modules *lua-stdlib* diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 3621f90511..9fe92a92cc 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -422,6 +422,7 @@ static struct vimvar { VV(VV_TYPE_BOOL, "t_bool", VAR_NUMBER, VV_RO), VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO), VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO), + VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO), }; #undef VV @@ -433,11 +434,14 @@ static struct vimvar { #define vv_str vv_di.di_tv.vval.v_string #define vv_list vv_di.di_tv.vval.v_list #define vv_dict vv_di.di_tv.vval.v_dict +#define vv_partial vv_di.di_tv.vval.v_partial #define vv_tv vv_di.di_tv /// Variable used for v: static ScopeDictDictItem vimvars_var; +static partial_T *vvlua_partial; + /// v: hashtab #define vimvarht vimvardict.dv_hashtab @@ -639,6 +643,13 @@ void eval_init(void) set_vim_var_nr(VV_ECHOSPACE, sc_col - 1); + vimvars[VV_LUA].vv_type = VAR_PARTIAL; + vvlua_partial = xcalloc(1, sizeof(partial_T)); + vimvars[VV_LUA].vv_partial = vvlua_partial; + // this value shouldn't be printed, but if it is, do not crash + vvlua_partial->pt_name = xmallocz(0); + vvlua_partial->pt_refcount++; + set_reg_var(0); // default for v:register is not 0 but '"' } @@ -1313,12 +1324,25 @@ int call_vim_function( { int doesrange; int ret; + int len = (int)STRLEN(func); + partial_T *pt = NULL; + + if (len >= 6 && !memcmp(func, "v:lua.", 6)) { + func += 6; + len = check_luafunc_name((const char *)func, false); + if (len == 0) { + ret = FAIL; + goto fail; + } + pt = vvlua_partial; + } rettv->v_type = VAR_UNKNOWN; // tv_clear() uses this. - ret = call_func(func, (int)STRLEN(func), rettv, argc, argv, NULL, + ret = call_func(func, len, rettv, argc, argv, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &doesrange, true, NULL, NULL); + &doesrange, true, pt, NULL); +fail: if (ret == FAIL) { tv_clear(rettv); } @@ -2462,6 +2486,13 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } } + if (lp->ll_di != NULL && tv_is_luafunc(&lp->ll_di->di_tv) + && len == -1 && rettv == NULL) { + tv_clear(&var1); + EMSG2(e_illvar, "v:['lua']"); + return NULL; + } + if (lp->ll_di == NULL) { // Can't add "v:" or "a:" variable. if (lp->ll_dict == &vimvardict @@ -4699,7 +4730,7 @@ eval_index( if (evaluate) { n1 = 0; - if (!empty1 && rettv->v_type != VAR_DICT) { + if (!empty1 && rettv->v_type != VAR_DICT && !tv_is_luafunc(rettv)) { n1 = tv_get_number(&var1); tv_clear(&var1); } @@ -4823,7 +4854,7 @@ eval_index( if (len == -1) { tv_clear(&var1); } - if (item == NULL) { + if (item == NULL || tv_is_luafunc(&item->di_tv)) { return FAIL; } @@ -6334,7 +6365,7 @@ static char_u *deref_func_name(const char *name, int *lenp, */ static int get_func_tv( - char_u *name, // name of the function + const char_u *name, // name of the function int len, // length of "name" typval_T *rettv, char_u **arg, // argument, pointing to the '(' @@ -6590,7 +6621,15 @@ call_func( rettv->vval.v_number = 0; error = ERROR_UNKNOWN; - if (!builtin_function((const char *)rfname, -1)) { + if (partial == vvlua_partial) { + if (len > 0) { + error = ERROR_NONE; + executor_call_lua((const char *)funcname, len, + argvars, argcount, rettv); + } else { + error = ERROR_UNKNOWN; + } + } else if (!builtin_function((const char *)rfname, -1)) { // User defined function. if (partial != NULL && partial->pt_func != NULL) { fp = partial->pt_func; @@ -6707,14 +6746,14 @@ call_func( /// /// @param ermsg must be passed without translation (use N_() instead of _()). /// @param name function name -static void emsg_funcname(char *ermsg, char_u *name) +static void emsg_funcname(char *ermsg, const char_u *name) { char_u *p; if (*name == K_SPECIAL) { p = concat_str((char_u *)"", name + 3); } else { - p = name; + p = (char_u *)name; } EMSG2(_(ermsg), p); @@ -20168,6 +20207,26 @@ static void check_vars(const char *name, size_t len) } } +/// check if special v:lua value for calling lua functions +static bool tv_is_luafunc(typval_T *tv) +{ + return tv->v_type == VAR_PARTIAL && tv->vval.v_partial == vvlua_partial; +} + +/// check the function name after "v:lua." +static int check_luafunc_name(const char *str, bool paren) +{ + const char *p = str; + while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.') { + p++; + } + if (*p != (paren ? '(' : NUL)) { + return 0; + } else { + return (int)(p-str); + } +} + /// Handle expr[expr], expr[expr:expr] subscript and .name lookup. /// Also handle function call with Funcref variable: func(expr) /// Can all be combined: dict.func(expr)[idx]['func'](expr) @@ -20181,9 +20240,30 @@ handle_subscript( { int ret = OK; dict_T *selfdict = NULL; - char_u *s; + const char_u *s; int len; typval_T functv; + int slen = 0; + bool lua = false; + + if (tv_is_luafunc(rettv)) { + if (**arg != '.') { + tv_clear(rettv); + ret = FAIL; + } else { + (*arg)++; + + lua = true; + s = (char_u *)(*arg); + slen = check_luafunc_name(*arg, true); + if (slen == 0) { + tv_clear(rettv); + ret = FAIL; + } + (*arg) += slen; + } + } + while (ret == OK && (**arg == '[' @@ -20200,14 +20280,16 @@ handle_subscript( // Invoke the function. Recursive! if (functv.v_type == VAR_PARTIAL) { pt = functv.vval.v_partial; - s = partial_name(pt); + if (!lua) { + s = partial_name(pt); + } } else { s = functv.vval.v_string; } } else { s = (char_u *)""; } - ret = get_func_tv(s, (int)STRLEN(s), rettv, (char_u **)arg, + ret = get_func_tv(s, lua ? slen : (int)STRLEN(s), rettv, (char_u **)arg, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &len, evaluate, pt, selfdict); @@ -22039,8 +22121,19 @@ trans_function_name( *pp = (char_u *)end; } else if (lv.ll_tv->v_type == VAR_PARTIAL && lv.ll_tv->vval.v_partial != NULL) { - name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial)); - *pp = (char_u *)end; + if (lv.ll_tv->vval.v_partial == vvlua_partial && *end == '.') { + len = check_luafunc_name((const char *)end+1, true); + if (len == 0) { + EMSG2(e_invexpr2, "v:lua"); + goto theend; + } + name = xmallocz(len); + memcpy(name, end+1, len); + *pp = (char_u *)end+1+len; + } else { + name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial)); + *pp = (char_u *)end; + } if (partial != NULL) { *partial = lv.ll_tv->vval.v_partial; } diff --git a/src/nvim/eval.h b/src/nvim/eval.h index e099de831a..2aa08e2074 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -117,6 +117,7 @@ typedef enum { VV_TYPE_BOOL, VV_ECHOSPACE, VV_EXITING, + VV_LUA, } VimVarIndex; /// All recognized msgpack types diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 5e5cb0cea5..093c130c5f 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -48,9 +48,6 @@ typedef struct { # include "lua/executor.c.generated.h" #endif -/// Name of the run code for use in messages -#define NLUA_EVAL_NAME "" - /// Convert lua error into a Vim error message /// /// @param lstate Lua interpreter state. @@ -397,29 +394,6 @@ static lua_State *nlua_enter(void) return lstate; } -/// Execute lua string -/// -/// @param[in] str String to execute. -/// @param[out] ret_tv Location where result will be saved. -/// -/// @return Result of the execution. -void executor_exec_lua(const String str, typval_T *const ret_tv) - FUNC_ATTR_NONNULL_ALL -{ - lua_State *const lstate = nlua_enter(); - - if (luaL_loadbuffer(lstate, str.data, str.size, NLUA_EVAL_NAME)) { - nlua_error(lstate, _("E5104: Error while creating lua chunk: %.*s")); - return; - } - if (lua_pcall(lstate, 0, 1, 0)) { - nlua_error(lstate, _("E5105: Error while calling lua chunk: %.*s")); - return; - } - - nlua_pop_typval(lstate, ret_tv); -} - static void nlua_print_event(void **argv) { char *str = argv[0]; @@ -732,10 +706,6 @@ void executor_eval_lua(const String str, typval_T *const arg, typval_T *const ret_tv) FUNC_ATTR_NONNULL_ALL { - lua_State *const lstate = nlua_enter(); - - garray_T str_ga; - ga_init(&str_ga, 1, 80); #define EVALHEADER "local _A=select(1,...) return (" const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str.size + 1; char *lcmd; @@ -748,30 +718,71 @@ void executor_eval_lua(const String str, typval_T *const arg, memcpy(lcmd + sizeof(EVALHEADER) - 1, str.data, str.size); lcmd[lcmd_len - 1] = ')'; #undef EVALHEADER - if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { - nlua_error(lstate, - _("E5107: Error while creating lua chunk for luaeval(): %.*s")); - if (lcmd != (char *)IObuff) { - xfree(lcmd); - } - return; - } + typval_exec_lua(lcmd, lcmd_len, "luaeval()", arg, 1, true, ret_tv); + if (lcmd != (char *)IObuff) { xfree(lcmd); } +} - if (arg->v_type == VAR_UNKNOWN) { - lua_pushnil(lstate); +void executor_call_lua(const char *str, size_t len, typval_T *const args, + int argcount, typval_T *ret_tv) + FUNC_ATTR_NONNULL_ALL +{ +#define CALLHEADER "return " +#define CALLSUFFIX "(...)" + const size_t lcmd_len = sizeof(CALLHEADER) - 1 + len + sizeof(CALLSUFFIX) - 1; + char *lcmd; + if (lcmd_len < IOSIZE) { + lcmd = (char *)IObuff; } else { - nlua_push_typval(lstate, arg, true); + lcmd = xmalloc(lcmd_len); } - if (lua_pcall(lstate, 1, 1, 0)) { - nlua_error(lstate, - _("E5108: Error while calling lua chunk for luaeval(): %.*s")); + memcpy(lcmd, CALLHEADER, sizeof(CALLHEADER) - 1); + memcpy(lcmd + sizeof(CALLHEADER) - 1, str, len); + memcpy(lcmd + sizeof(CALLHEADER) - 1 + len, CALLSUFFIX, + sizeof(CALLSUFFIX) - 1); +#undef CALLHEADER +#undef CALLSUFFIX + + typval_exec_lua(lcmd, lcmd_len, "v:lua", args, argcount, false, ret_tv); + + if (lcmd != (char *)IObuff) { + xfree(lcmd); + } +} + +static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name, + typval_T *const args, int argcount, bool special, + typval_T *ret_tv) +{ + if (check_restricted() || check_secure()) { + ret_tv->v_type = VAR_NUMBER; + ret_tv->vval.v_number = 0; return; } - nlua_pop_typval(lstate, ret_tv); + lua_State *const lstate = nlua_enter(); + if (luaL_loadbuffer(lstate, lcmd, lcmd_len, name)) { + nlua_error(lstate, _("E5107: Error loading lua %.*s")); + return; + } + + for (int i = 0; i < argcount; i++) { + if (args[i].v_type == VAR_UNKNOWN) { + lua_pushnil(lstate); + } else { + nlua_push_typval(lstate, &args[i], special); + } + } + if (lua_pcall(lstate, argcount, ret_tv ? 1 : 0, 0)) { + nlua_error(lstate, _("E5108: Error executing lua %.*s")); + return; + } + + if (ret_tv) { + nlua_pop_typval(lstate, ret_tv); + } } /// Execute lua string @@ -857,9 +868,8 @@ void ex_lua(exarg_T *const eap) xfree(code); return; } - typval_T tv = { .v_type = VAR_UNKNOWN }; - executor_exec_lua((String) { .data = code, .size = len }, &tv); - tv_clear(&tv); + typval_exec_lua(code, len, ":lua", NULL, 0, false, NULL); + xfree(code); } @@ -897,8 +907,8 @@ void ex_luado(exarg_T *const eap) #undef DOSTART #undef DOEND - if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { - nlua_error(lstate, _("E5109: Error while creating lua chunk: %.*s")); + if (luaL_loadbuffer(lstate, lcmd, lcmd_len, ":luado")) { + nlua_error(lstate, _("E5109: Error loading lua: %.*s")); if (lcmd_len >= IOSIZE) { xfree(lcmd); } @@ -908,7 +918,7 @@ void ex_luado(exarg_T *const eap) xfree(lcmd); } if (lua_pcall(lstate, 0, 1, 0)) { - nlua_error(lstate, _("E5110: Error while creating lua function: %.*s")); + nlua_error(lstate, _("E5110: Error executing lua: %.*s")); return; } for (linenr_T l = eap->line1; l <= eap->line2; l++) { @@ -919,7 +929,7 @@ void ex_luado(exarg_T *const eap) lua_pushstring(lstate, (const char *)ml_get_buf(curbuf, l, false)); lua_pushnumber(lstate, (lua_Number)l); if (lua_pcall(lstate, 2, 1, 0)) { - nlua_error(lstate, _("E5111: Error while calling lua function: %.*s")); + nlua_error(lstate, _("E5111: Error calling lua: %.*s")); break; } if (lua_isstring(lstate, -1)) { diff --git a/test/functional/lua/api_spec.lua b/test/functional/lua/api_spec.lua index b1dc5c07fd..23167d3ed9 100644 --- a/test/functional/lua/api_spec.lua +++ b/test/functional/lua/api_spec.lua @@ -155,41 +155,41 @@ describe('luaeval(vim.api.…)', function() it('errors out correctly when working with API', function() -- Conversion errors - eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string ""]:1: Cannot convert given lua type', + eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Cannot convert given lua type', exc_exec([[call luaeval("vim.api.nvim__id(vim.api.nvim__id)")]])) - eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string ""]:1: Cannot convert given lua table', + eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Cannot convert given lua table', exc_exec([[call luaeval("vim.api.nvim__id({1, foo=42})")]])) - eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string ""]:1: Cannot convert given lua type', + eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Cannot convert given lua type', exc_exec([[call luaeval("vim.api.nvim__id({42, vim.api.nvim__id})")]])) -- Errors in number of arguments - eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string ""]:1: Expected 1 argument', + eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected 1 argument', exc_exec([[call luaeval("vim.api.nvim__id()")]])) - eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string ""]:1: Expected 1 argument', + eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected 1 argument', exc_exec([[call luaeval("vim.api.nvim__id(1, 2)")]])) - eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string ""]:1: Expected 2 arguments', + eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected 2 arguments', exc_exec([[call luaeval("vim.api.nvim_set_var(1, 2, 3)")]])) -- Error in argument types - eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string ""]:1: Expected lua string', + eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua string', exc_exec([[call luaeval("vim.api.nvim_set_var(1, 2)")]])) - eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string ""]:1: Expected lua number', + eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua number', exc_exec([[call luaeval("vim.api.nvim_buf_get_lines(0, 'test', 1, false)")]])) - eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string ""]:1: Number is not integral', + eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Number is not integral', exc_exec([[call luaeval("vim.api.nvim_buf_get_lines(0, 1.5, 1, false)")]])) - eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string ""]:1: Expected lua table', + eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua table', exc_exec([[call luaeval("vim.api.nvim__id_float('test')")]])) - eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string ""]:1: Unexpected type', + eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Unexpected type', exc_exec([[call luaeval("vim.api.nvim__id_float({[vim.type_idx]=vim.types.dictionary})")]])) - eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string ""]:1: Expected lua table', + eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua table', exc_exec([[call luaeval("vim.api.nvim__id_array(1)")]])) - eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string ""]:1: Unexpected type', + eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Unexpected type', exc_exec([[call luaeval("vim.api.nvim__id_array({[vim.type_idx]=vim.types.dictionary})")]])) - eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string ""]:1: Expected lua table', + eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua table', exc_exec([[call luaeval("vim.api.nvim__id_dictionary(1)")]])) - eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string ""]:1: Unexpected type', + eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Unexpected type', exc_exec([[call luaeval("vim.api.nvim__id_dictionary({[vim.type_idx]=vim.types.array})")]])) -- TODO: check for errors with Tabpage argument -- TODO: check for errors with Window argument diff --git a/test/functional/lua/commands_spec.lua b/test/functional/lua/commands_spec.lua index 26dcbe0534..96eaa7991b 100644 --- a/test/functional/lua/commands_spec.lua +++ b/test/functional/lua/commands_spec.lua @@ -13,6 +13,7 @@ local source = helpers.source local dedent = helpers.dedent local command = helpers.command local exc_exec = helpers.exc_exec +local pcall_err = helpers.pcall_err local write_file = helpers.write_file local redir_exec = helpers.redir_exec local curbufmeths = helpers.curbufmeths @@ -42,16 +43,16 @@ describe(':lua command', function() eq({'', 'ETTS', 'TTSE', 'STTE'}, curbufmeths.get_lines(0, 100, false)) end) it('throws catchable errors', function() - eq([[Vim(lua):E5104: Error while creating lua chunk: [string ""]:1: unexpected symbol near ')']], - exc_exec('lua ()')) - eq([[Vim(lua):E5105: Error while calling lua chunk: [string ""]:1: TEST]], + eq([[Vim(lua):E5107: Error loading lua [string ":lua"]:1: unexpected symbol near ')']], + pcall_err(command, 'lua ()')) + eq([[Vim(lua):E5108: Error executing lua [string ":lua"]:1: TEST]], exc_exec('lua error("TEST")')) - eq([[Vim(lua):E5105: Error while calling lua chunk: [string ""]:1: Invalid buffer id]], + eq([[Vim(lua):E5108: Error executing lua [string ":lua"]:1: Invalid buffer id]], exc_exec('lua vim.api.nvim_buf_set_lines(-10, 1, 1, false, {"TEST"})')) eq({''}, curbufmeths.get_lines(0, 100, false)) end) it('works with NULL errors', function() - eq([=[Vim(lua):E5105: Error while calling lua chunk: [NULL]]=], + eq([=[Vim(lua):E5108: Error executing lua [NULL]]=], exc_exec('lua error(nil)')) end) it('accepts embedded NLs without heredoc', function() @@ -74,7 +75,7 @@ describe(':lua command', function() it('works with long strings', function() local s = ('x'):rep(100500) - eq('\nE5104: Error while creating lua chunk: [string ""]:1: unfinished string near \'\'', redir_exec(('lua vim.api.nvim_buf_set_lines(1, 1, 2, false, {"%s})'):format(s))) + eq('\nE5107: Error loading lua [string ":lua"]:1: unfinished string near \'\'', redir_exec(('lua vim.api.nvim_buf_set_lines(1, 1, 2, false, {"%s})'):format(s))) eq({''}, curbufmeths.get_lines(0, -1, false)) eq('', redir_exec(('lua vim.api.nvim_buf_set_lines(1, 1, 2, false, {"%s"})'):format(s))) @@ -82,7 +83,7 @@ describe(':lua command', function() end) it('can show multiline error messages', function() - local screen = Screen.new(50,10) + local screen = Screen.new(40,10) screen:attach() screen:set_default_attr_ids({ [1] = {bold = true, foreground = Screen.colors.Blue1}, @@ -92,51 +93,51 @@ describe(':lua command', function() }) feed(':lua error("fail\\nmuch error\\nsuch details")') - screen:expect([[ - | - {1:~ }| - {1:~ }| - {1:~ }| - {2: }| - {3:E5105: Error while calling lua chunk: [string ""]:1: fail} | - {3:much error} | - {3:such details} | - {4:Press ENTER or type command to continue}^ | - ]]) + screen:expect{grid=[[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + {3:E5108: Error executing lua [string ":lua}| + {3:"]:1: fail} | + {3:much error} | + {3:such details} | + {4:Press ENTER or type command to continue}^ | + ]]} feed('') - screen:expect([[ - ^ | - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - | - ]]) - eq('E5105: Error while calling lua chunk: [string ""]:1: fail\nmuch error\nsuch details', eval('v:errmsg')) + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + eq('E5108: Error executing lua [string ":lua"]:1: fail\nmuch error\nsuch details', eval('v:errmsg')) local status, err = pcall(command,'lua error("some error\\nin a\\nAPI command")') - local expected = 'Vim(lua):E5105: Error while calling lua chunk: [string ""]:1: some error\nin a\nAPI command' + local expected = 'Vim(lua):E5108: Error executing lua [string ":lua"]:1: some error\nin a\nAPI command' eq(false, status) eq(expected, string.sub(err, -string.len(expected))) feed(':messages') - screen:expect([[ - | - {1:~ }| - {1:~ }| - {1:~ }| - {2: }| - {3:E5105: Error while calling lua chunk: [string ""]:1: fail} | - {3:much error} | - {3:such details} | - {4:Press ENTER or type command to continue}^ | - ]]) + screen:expect{grid=[[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + {3:E5108: Error executing lua [string ":lua}| + {3:"]:1: fail} | + {3:much error} | + {3:such details} | + {4:Press ENTER or type command to continue}^ | + ]]} end) end) @@ -167,13 +168,13 @@ describe(':luado command', function() eq({''}, curbufmeths.get_lines(0, -1, false)) end) it('fails on errors', function() - eq([[Vim(luado):E5109: Error while creating lua chunk: [string ""]:1: unexpected symbol near ')']], + eq([[Vim(luado):E5109: Error loading lua: [string ":luado"]:1: unexpected symbol near ')']], exc_exec('luado ()')) - eq([[Vim(luado):E5111: Error while calling lua function: [string ""]:1: attempt to perform arithmetic on global 'liness' (a nil value)]], + eq([[Vim(luado):E5111: Error calling lua: [string ":luado"]:1: attempt to perform arithmetic on global 'liness' (a nil value)]], exc_exec('luado return liness + 1')) end) it('works with NULL errors', function() - eq([=[Vim(luado):E5111: Error while calling lua function: [NULL]]=], + eq([=[Vim(luado):E5111: Error calling lua: [NULL]]=], exc_exec('luado error(nil)')) end) it('fails in sandbox when needed', function() @@ -185,7 +186,7 @@ describe(':luado command', function() it('works with long strings', function() local s = ('x'):rep(100500) - eq('\nE5109: Error while creating lua chunk: [string ""]:1: unfinished string near \'\'', redir_exec(('luado return "%s'):format(s))) + eq('\nE5109: Error loading lua: [string ":luado"]:1: unfinished string near \'\'', redir_exec(('luado return "%s'):format(s))) eq({''}, curbufmeths.get_lines(0, -1, false)) eq('', redir_exec(('luado return "%s"'):format(s))) diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua index 760105df6b..61c8e5c02e 100644 --- a/test/functional/lua/luaeval_spec.lua +++ b/test/functional/lua/luaeval_spec.lua @@ -1,13 +1,17 @@ -- Test suite for testing luaeval() function local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local redir_exec = helpers.redir_exec +local pcall_err = helpers.pcall_err local exc_exec = helpers.exc_exec +local exec_lua = helpers.exec_lua local command = helpers.command local meths = helpers.meths local funcs = helpers.funcs local clear = helpers.clear local eval = helpers.eval +local feed = helpers.feed local NIL = helpers.NIL local eq = helpers.eq @@ -186,9 +190,9 @@ describe('luaeval()', function() exc_exec('call luaeval("{1, foo=2}")')) eq("Vim(call):E5101: Cannot convert given lua type", exc_exec('call luaeval("vim.api.nvim_buf_get_lines")')) - startswith("Vim(call):E5107: Error while creating lua chunk for luaeval(): ", + startswith("Vim(call):E5107: Error loading lua [string \"luaeval()\"]:", exc_exec('call luaeval("1, 2, 3")')) - startswith("Vim(call):E5108: Error while calling lua chunk for luaeval(): ", + startswith("Vim(call):E5108: Error executing lua [string \"luaeval()\"]:", exc_exec('call luaeval("(nil)()")')) eq("Vim(call):E5101: Cannot convert given lua type", exc_exec('call luaeval("{42, vim.api}")')) @@ -237,19 +241,99 @@ describe('luaeval()', function() it('errors out correctly when doing incorrect things in lua', function() -- Conversion errors - eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string ""]:1: attempt to call field \'xxx_nonexistent_key_xxx\' (a nil value)', + eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: attempt to call field \'xxx_nonexistent_key_xxx\' (a nil value)', exc_exec([[call luaeval("vim.xxx_nonexistent_key_xxx()")]])) - eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string ""]:1: ERROR', + eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: ERROR', exc_exec([[call luaeval("error('ERROR')")]])) - eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [NULL]', + eq('Vim(call):E5108: Error executing lua [NULL]', exc_exec([[call luaeval("error(nil)")]])) end) it('does not leak memory when called with too long line', function() local s = ('x'):rep(65536) - eq('Vim(call):E5107: Error while creating lua chunk for luaeval(): [string ""]:1: unexpected symbol near \')\'', + eq('Vim(call):E5107: Error loading lua [string "luaeval()"]:1: unexpected symbol near \')\'', exc_exec([[call luaeval("(']] .. s ..[[' + )")]])) eq(s, funcs.luaeval('"' .. s .. '"')) end) end) + +describe('v:lua', function() + before_each(function() + exec_lua([[ + function _G.foo(a,b,n) + _G.val = n + return a+b + end + mymod = {} + function mymod.noisy(name) + vim.api.nvim_set_current_line("hey "..name) + end + function mymod.crashy() + nonexistent() + end + function mymod.omni(findstart, base) + if findstart == 1 then + return 5 + else + if base == 'st' then + return {'stuff', 'steam', 'strange things'} + end + end + end + vim.api.nvim_buf_set_option(0, 'omnifunc', 'v:lua.mymod.omni') + ]]) + end) + + it('works in expressions', function() + eq(7, eval('v:lua.foo(3,4,v:null)')) + eq(true, exec_lua([[return _G.val == vim.NIL]])) + eq(NIL, eval('v:lua.mymod.noisy("eval")')) + eq("hey eval", meths.get_current_line()) + + eq("Vim:E5108: Error executing lua [string \"\"]:10: attempt to call global 'nonexistent' (a nil value)", + pcall_err(eval, 'v:lua.mymod.crashy()')) + end) + + it('works in :call', function() + command(":call v:lua.mymod.noisy('command')") + eq("hey command", meths.get_current_line()) + eq("Vim(call):E5108: Error executing lua [string \"\"]:10: attempt to call global 'nonexistent' (a nil value)", + pcall_err(command, 'call v:lua.mymod.crashy()')) + end) + + it('works in func options', function() + local screen = Screen.new(60, 8) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {background = Screen.colors.WebGray}, + [3] = {background = Screen.colors.LightMagenta}, + [4] = {bold = true}, + [5] = {bold = true, foreground = Screen.colors.SeaGreen4}, + }) + screen:attach() + feed('isome st') + screen:expect{grid=[[ + some stuff^ | + {1:~ }{2: stuff }{1: }| + {1:~ }{3: steam }{1: }| + {1:~ }{3: strange things }{1: }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- Omni completion (^O^N^P) }{5:match 1 of 3} | + ]]} + end) + + it('throw errors for invalid use', function() + eq('Vim(let):E15: Invalid expression: v:lua.func', pcall_err(command, "let g:Func = v:lua.func")) + eq('Vim(let):E15: Invalid expression: v:lua', pcall_err(command, "let g:Func = v:lua")) + eq("Vim(let):E15: Invalid expression: v:['lua']", pcall_err(command, "let g:Func = v:['lua']")) + + eq("Vim:E15: Invalid expression: v:['lua'].foo()", pcall_err(eval, "v:['lua'].foo()")) + eq("Vim(call):E461: Illegal variable name: v:['lua']", pcall_err(command, "call v:['lua'].baar()")) + + 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'")) + end) +end) diff --git a/test/functional/lua/overrides_spec.lua b/test/functional/lua/overrides_spec.lua index f6439001ac..8c260632d9 100644 --- a/test/functional/lua/overrides_spec.lua +++ b/test/functional/lua/overrides_spec.lua @@ -54,11 +54,12 @@ describe('print', function() v_tblout = setmetatable({}, meta_tblout) ]]) eq('', redir_exec('luafile ' .. fname)) - eq('\nE5105: Error while calling lua chunk: E5114: Error while converting print argument #2: [NULL]', + -- TODO(bfredl): these look weird, print() should not use "E5114:" style errors.. + eq('\nE5108: Error executing lua E5114: Error while converting print argument #2: [NULL]', redir_exec('lua print("foo", v_nilerr, "bar")')) - eq('\nE5105: Error while calling lua chunk: E5114: Error while converting print argument #2: Xtest-functional-lua-overrides-luafile:2: abc', + eq('\nE5108: Error executing lua E5114: Error while converting print argument #2: Xtest-functional-lua-overrides-luafile:2: abc', redir_exec('lua print("foo", v_abcerr, "bar")')) - eq('\nE5105: Error while calling lua chunk: E5114: Error while converting print argument #2: ', + eq('\nE5108: Error executing lua E5114: Error while converting print argument #2: ', redir_exec('lua print("foo", v_tblout, "bar")')) end) it('prints strings with NULs and NLs correctly', function() @@ -156,7 +157,8 @@ describe('debug.debug', function() lua_debug> ^ | ]]) feed('') - screen:expect([[ + screen:expect{grid=[[ + {0:~ }| {0:~ }| {0:~ }| {0:~ }| @@ -167,11 +169,10 @@ describe('debug.debug', function() lua_debug> print("TEST") | TEST | | - {E:E5105: Error while calling lua chunk: [string ""]:5: attempt to perform arithmetic o}| - {E:n local 'a' (a nil value)} | + {E:E5108: Error executing lua [string ":lua"]:5: attempt}| + {E: to perform arithmetic on local 'a' (a nil value)} | Interrupt: {cr:Press ENTER or type command to continue}^ | - ]]) + ]]} feed(':lua Test()\n') screen:expect([[ {0:~ }| @@ -190,7 +191,8 @@ describe('debug.debug', function() lua_debug> ^ | ]]) feed('\n') - screen:expect([[ + screen:expect{grid=[[ + {0:~ }| {0:~ }| {0:~ }| {0:~ }| @@ -201,11 +203,10 @@ describe('debug.debug', function() {0:~ }| nil | lua_debug> | - {E:E5105: Error while calling lua chunk: [string ""]:5: attempt to perform arithmetic o}| - {E:n local 'a' (a nil value)} | + {E:E5108: Error executing lua [string ":lua"]:5: attempt}| + {E: to perform arithmetic on local 'a' (a nil value)} | {cr:Press ENTER or type command to continue}^ | - ]]) + ]]} end) it("can be safely exited with 'cont'", function() diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 8ad3aff21f..40ea030f73 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -747,7 +747,7 @@ describe('ui/ext_messages', function() {1:~ }| {1:~ }| ]], messages={{ - content = {{'E5105: Error while calling lua chunk: [string ""]:1: such\nmultiline\nerror', 2}}, + content = {{'E5108: Error executing lua [string ":lua"]:1: such\nmultiline\nerror', 2}}, kind = "lua_error" }}} end) @@ -1146,97 +1146,96 @@ aliquip ex ea commodo consequat.]]) it('handles wrapped lines with line scroll', function() feed(':lua error(_G.x)') screen:expect{grid=[[ - {2:E5105: Error while calling lua chun}| - {2:k: [string ""}| - {2:]:1: Lorem ipsum dolor sit amet, co}| - {2:nsectetur} | + {2:E5108: Error executing lua [string }| + {2:":lua"]:1: Lorem ipsum dolor sit am}| + {2:et, consectetur} | {2:adipisicing elit, sed do eiusmod te}| {2:mpor} | {2:incididunt ut labore et dolore magn}| + {2:a aliqua.} | {4:-- More --}^ | ]]} feed('j') screen:expect{grid=[[ - {2:k: [string ""}| - {2:]:1: Lorem ipsum dolor sit amet, co}| - {2:nsectetur} | + {2:":lua"]:1: Lorem ipsum dolor sit am}| + {2:et, consectetur} | {2:adipisicing elit, sed do eiusmod te}| {2:mpor} | {2:incididunt ut labore et dolore magn}| {2:a aliqua.} | + {2:Ut enim ad minim veniam, quis nostr}| {4:-- More --}^ | ]]} feed('k') screen:expect{grid=[[ - {2:E5105: Error while calling lua chun}| - {2:k: [string ""}| - {2:]:1: Lorem ipsum dolor sit amet, co}| - {2:nsectetur} | + {2:E5108: Error executing lua [string }| + {2:":lua"]:1: Lorem ipsum dolor sit am}| + {2:et, consectetur} | {2:adipisicing elit, sed do eiusmod te}| {2:mpor} | {2:incididunt ut labore et dolore magn}| + {2:a aliqua.} | {4:-- More --}^ | ]]} feed('j') screen:expect{grid=[[ - {2:k: [string ""}| - {2:]:1: Lorem ipsum dolor sit amet, co}| - {2:nsectetur} | + {2:":lua"]:1: Lorem ipsum dolor sit am}| + {2:et, consectetur} | {2:adipisicing elit, sed do eiusmod te}| {2:mpor} | {2:incididunt ut labore et dolore magn}| {2:a aliqua.} | + {2:Ut enim ad minim veniam, quis nostr}| {4:-- More --}^ | ]]} - end) it('handles wrapped lines with page scroll', function() feed(':lua error(_G.x)') screen:expect{grid=[[ - {2:E5105: Error while calling lua chun}| - {2:k: [string ""}| - {2:]:1: Lorem ipsum dolor sit amet, co}| - {2:nsectetur} | + {2:E5108: Error executing lua [string }| + {2:":lua"]:1: Lorem ipsum dolor sit am}| + {2:et, consectetur} | {2:adipisicing elit, sed do eiusmod te}| {2:mpor} | {2:incididunt ut labore et dolore magn}| + {2:a aliqua.} | {4:-- More --}^ | ]]} feed('d') screen:expect{grid=[[ - {2:adipisicing elit, sed do eiusmod te}| - {2:mpor} | {2:incididunt ut labore et dolore magn}| {2:a aliqua.} | {2:Ut enim ad minim veniam, quis nostr}| {2:ud xercitation} | {2:ullamco laboris nisi ut} | - {4:-- More --}^ | + {2:aliquip ex ea commodo consequat.} | + {4:Press ENTER or type command to cont}| + {4:inue}^ | ]]} feed('u') screen:expect{grid=[[ - {2:E5105: Error while calling lua chun}| - {2:k: [string ""}| - {2:]:1: Lorem ipsum dolor sit amet, co}| - {2:nsectetur} | + {2:E5108: Error executing lua [string }| + {2:":lua"]:1: Lorem ipsum dolor sit am}| + {2:et, consectetur} | {2:adipisicing elit, sed do eiusmod te}| {2:mpor} | {2:incididunt ut labore et dolore magn}| + {2:a aliqua.} | {4:-- More --}^ | ]]} feed('d') screen:expect{grid=[[ - {2:adipisicing elit, sed do eiusmod te}| {2:mpor} | {2:incididunt ut labore et dolore magn}| {2:a aliqua.} | {2:Ut enim ad minim veniam, quis nostr}| {2:ud xercitation} | {2:ullamco laboris nisi ut} | + {2:aliquip ex ea commodo consequat.} | {4:-- More --}^ | ]]} end) @@ -1246,49 +1245,49 @@ aliquip ex ea commodo consequat.]]) feed(':lua error(_G.x)') screen:expect{grid=[[ - {3:E5105: Error while calling lua chun}| - {3:k: [string ""}| - {3:]:1: Lorem ipsum dolor sit amet, co}| - {3:nsectetur}{5: }| + {3:E5108: Error executing lua [string }| + {3:":lua"]:1: Lorem ipsum dolor sit am}| + {3:et, consectetur}{5: }| {3:adipisicing elit, sed do eiusmod te}| {3:mpor}{5: }| {3:incididunt ut labore et dolore magn}| + {3:a aliqua.}{5: }| {6:-- More --}{5:^ }| ]]} feed('j') screen:expect{grid=[[ - {3:k: [string ""}| - {3:]:1: Lorem ipsum dolor sit amet, co}| - {3:nsectetur}{5: }| + {3:":lua"]:1: Lorem ipsum dolor sit am}| + {3:et, consectetur}{5: }| {3:adipisicing elit, sed do eiusmod te}| {3:mpor}{5: }| {3:incididunt ut labore et dolore magn}| {3:a aliqua.}{5: }| + {3:Ut enim ad minim veniam, quis nostr}| {6:-- More --}{5:^ }| ]]} feed('k') screen:expect{grid=[[ - {3:E5105: Error while calling lua chun}| - {3:k: [string ""}| - {3:]:1: Lorem ipsum dolor sit amet, co}| - {3:nsectetur}{5: }| + {3:E5108: Error executing lua [string }| + {3:":lua"]:1: Lorem ipsum dolor sit am}| + {3:et, consectetur}{5: }| {3:adipisicing elit, sed do eiusmod te}| {3:mpor}{5: }| {3:incididunt ut labore et dolore magn}| + {3:a aliqua.}{5: }| {6:-- More --}{5:^ }| ]]} feed('j') screen:expect{grid=[[ - {3:k: [string ""}| - {3:]:1: Lorem ipsum dolor sit amet, co}| - {3:nsectetur}{5: }| + {3:":lua"]:1: Lorem ipsum dolor sit am}| + {3:et, consectetur}{5: }| {3:adipisicing elit, sed do eiusmod te}| {3:mpor}{5: }| {3:incididunt ut labore et dolore magn}| {3:a aliqua.}{5: }| + {3:Ut enim ad minim veniam, quis nostr}| {6:-- More --}{5:^ }| ]]} end) @@ -1297,46 +1296,46 @@ aliquip ex ea commodo consequat.]]) command("hi MsgArea guisp=Yellow") feed(':lua error(_G.x)') screen:expect{grid=[[ - {3:E5105: Error while calling lua chun}| - {3:k: [string ""}| - {3:]:1: Lorem ipsum dolor sit amet, co}| - {3:nsectetur}{5: }| + {3:E5108: Error executing lua [string }| + {3:":lua"]:1: Lorem ipsum dolor sit am}| + {3:et, consectetur}{5: }| {3:adipisicing elit, sed do eiusmod te}| {3:mpor}{5: }| {3:incididunt ut labore et dolore magn}| + {3:a aliqua.}{5: }| {6:-- More --}{5:^ }| ]]} feed('d') screen:expect{grid=[[ - {3:adipisicing elit, sed do eiusmod te}| - {3:mpor}{5: }| {3:incididunt ut labore et dolore magn}| {3:a aliqua.}{5: }| {3:Ut enim ad minim veniam, quis nostr}| {3:ud xercitation}{5: }| {3:ullamco laboris nisi ut}{5: }| - {6:-- More --}{5:^ }| + {3:aliquip ex ea commodo consequat.}{5: }| + {6:Press ENTER or type command to cont}| + {6:inue}{5:^ }| ]]} feed('u') screen:expect{grid=[[ - {3:E5105: Error while calling lua chun}| - {3:k: [string ""}| - {3:]:1: Lorem ipsum dolor sit amet, co}| - {3:nsectetur}{5: }| + {3:E5108: Error executing lua [string }| + {3:":lua"]:1: Lorem ipsum dolor sit am}| + {3:et, consectetur}{5: }| {3:adipisicing elit, sed do eiusmod te}| {3:mpor}{5: }| {3:incididunt ut labore et dolore magn}| + {3:a aliqua.}{5: }| {6:-- More --}{5:^ }| ]]} feed('d') screen:expect{grid=[[ - {3:adipisicing elit, sed do eiusmod te}| {3:mpor}{5: }| {3:incididunt ut labore et dolore magn}| {3:a aliqua.}{5: }| {3:Ut enim ad minim veniam, quis nostr}| {3:ud xercitation}{5: }| {3:ullamco laboris nisi ut}{5: }| + {3:aliquip ex ea commodo consequat.}{5: }| {6:-- More --}{5:^ }| ]]} end) @@ -1473,23 +1472,23 @@ aliquip ex ea commodo consequat.]]) it('can be resized', function() feed(':lua error(_G.x)') screen:expect{grid=[[ - {2:E5105: Error while calling lua chun}| - {2:k: [string ""}| - {2:]:1: Lorem ipsum dolor sit amet, co}| - {2:nsectetur} | + {2:E5108: Error executing lua [string }| + {2:":lua"]:1: Lorem ipsum dolor sit am}| + {2:et, consectetur} | {2:adipisicing elit, sed do eiusmod te}| {2:mpor} | {2:incididunt ut labore et dolore magn}| + {2:a aliqua.} | {4:-- More --}^ | ]]} -- responds to resize, but text is not reflown screen:try_resize(45, 5) screen:expect{grid=[[ - {2:nsectetur} | {2:adipisicing elit, sed do eiusmod te} | {2:mpor} | {2:incididunt ut labore et dolore magn} | + {2:a aliqua.} | {4:-- More --}^ | ]]} @@ -1497,14 +1496,14 @@ aliquip ex ea commodo consequat.]]) -- text is not reflown; existing lines get cut screen:try_resize(30, 12) screen:expect{grid=[[ - {2:E5105: Error while calling lua}| - {2:k: [string "') screen:expect{grid=[[ - {2:k: [string "