From dab40f43b18d35b283804ecb033310198cbf7548 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Wed, 30 Oct 2019 20:53:09 +0100 Subject: Add v:lua.func() vimL syntax for calling lua Also simplify error messages when calling lua from vimL. --- test/functional/lua/luaeval_spec.lua | 96 +++++++++++++++++++++++++++++++++--- 1 file changed, 90 insertions(+), 6 deletions(-) (limited to 'test/functional/lua/luaeval_spec.lua') 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) -- cgit From 971a191c4d772493535d55524b994fe385fae546 Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Fri, 19 Jun 2020 00:23:30 -0400 Subject: lua: Add ability to pass lua functions directly to vimL --- test/functional/lua/luaeval_spec.lua | 136 +++++++++++++++++++++++++++++++---- 1 file changed, 122 insertions(+), 14 deletions(-) (limited to 'test/functional/lua/luaeval_spec.lua') diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua index 61c8e5c02e..4faacecd86 100644 --- a/test/functional/lua/luaeval_spec.lua +++ b/test/functional/lua/luaeval_spec.lua @@ -2,7 +2,6 @@ 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 @@ -188,23 +187,132 @@ describe('luaeval()', function() it('issues an error in some cases', function() eq("Vim(call):E5100: Cannot convert given lua table: table should either have a sequence of positive integer keys or contain only string keys", 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 loading lua [string \"luaeval()\"]:", exc_exec('call luaeval("1, 2, 3")')) 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}")')) - eq("Vim(call):E5101: Cannot convert given lua type", - exc_exec('call luaeval("{foo=42, baz=vim.api}")')) - - -- The following should not crash: conversion error happens inside - eq("Vim(call):E5101: Cannot convert given lua type", - exc_exec('call luaeval("vim.api")')) - -- The following should not show internal error - eq("\nE5101: Cannot convert given lua type\n0", - redir_exec('echo luaeval("vim.api")')) + + end) + + it('should handle sending lua functions to viml', function() + eq(true, exec_lua [[ + can_pass_lua_callback_to_vim_from_lua_result = nil + + vim.fn.call(function() + can_pass_lua_callback_to_vim_from_lua_result = true + end, {}) + + return can_pass_lua_callback_to_vim_from_lua_result + ]]) + end) + + it('run functions even in timers', function() + eq(true, exec_lua [[ + can_pass_lua_callback_to_vim_from_lua_result = nil + + vim.fn.timer_start(50, function() + can_pass_lua_callback_to_vim_from_lua_result = true + end) + + vim.wait(1000, function() + return can_pass_lua_callback_to_vim_from_lua_result + end) + + return can_pass_lua_callback_to_vim_from_lua_result + ]]) + end) + + it('can run named functions more than once', function() + eq(5, exec_lua [[ + count_of_vals = 0 + + vim.fn.timer_start(5, function() + count_of_vals = count_of_vals + 1 + end, {['repeat'] = 5}) + + vim.fn.wait(1000, function() + return count_of_vals >= 5 + end) + + return count_of_vals + ]]) + end) + + it('can handle clashing names', function() + eq(1, exec_lua [[ + local f_loc = function() return 1 end + + local result = nil + vim.fn.timer_start(100, function() + result = f_loc() + end) + + local f_loc = function() return 2 end + vim.wait(1000, function() return result ~= nil end) + + return result + ]]) + end) + + it('should handle passing functions around', function() + command [[ + function VimCanCallLuaCallbacks(Concat, Cb) + let message = a:Concat("Hello Vim", "I'm Lua") + call a:Cb(message) + endfunction + ]] + + eq("Hello Vim I'm Lua", exec_lua [[ + can_pass_lua_callback_to_vim_from_lua_result = "" + + vim.fn.VimCanCallLuaCallbacks( + function(greeting, message) return greeting .. " " .. message end, + function(message) can_pass_lua_callback_to_vim_from_lua_result = message end + ) + + return can_pass_lua_callback_to_vim_from_lua_result + ]]) + end) + + it('should handle funcrefs', function() + command [[ + function VimCanCallLuaCallbacks(Concat, Cb) + let message = a:Concat("Hello Vim", "I'm Lua") + call a:Cb(message) + endfunction + ]] + + eq("Hello Vim I'm Lua", exec_lua [[ + can_pass_lua_callback_to_vim_from_lua_result = "" + + vim.funcref('VimCanCallLuaCallbacks')( + function(greeting, message) return greeting .. " " .. message end, + function(message) can_pass_lua_callback_to_vim_from_lua_result = message end + ) + + return can_pass_lua_callback_to_vim_from_lua_result + ]]) + end) + + -- TODO(tjdevries): Need to figure + pending('should work with metatables using __call', function() + eq(true, exec_lua [[ + local this_is_local_variable = false + local callable_table = setmetatable({}, { + __call = function(...) + this_is_local_variable = true + end + }) + + vim.fn.timer_start(5, callable_table) + + vim.wait(1000, function() + return this_is_local_variable + end) + + return this_is_local_variable + ]]) end) it('correctly converts containers with type_idx', function() -- cgit From 6360cf7ce87407bd8a519b9a17f45b2148291904 Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Tue, 30 Jun 2020 02:05:06 -0400 Subject: lua: Add ability to pass tables with __call vim-patch:8.2.1054: not so easy to pass a lua function to Vim vim-patch:8.2.1084: Lua: registering function has useless code I think I have also opened up the possibility for people to use these callbacks elsewhere, since I've added a new struct that we should be able to use. Also, this should allow us to determine what the state of a list is in Lua or a dictionary in Lua, since we now can track the luaref as we go. --- test/functional/lua/luaeval_spec.lua | 78 +++++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 6 deletions(-) (limited to 'test/functional/lua/luaeval_spec.lua') diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua index 4faacecd86..964ea4561e 100644 --- a/test/functional/lua/luaeval_spec.lua +++ b/test/functional/lua/luaeval_spec.lua @@ -295,13 +295,12 @@ describe('luaeval()', function() ]]) end) - -- TODO(tjdevries): Need to figure - pending('should work with metatables using __call', function() - eq(true, exec_lua [[ + it('should work with metatables using __call', function() + eq(1, exec_lua [[ local this_is_local_variable = false - local callable_table = setmetatable({}, { - __call = function(...) - this_is_local_variable = true + local callable_table = setmetatable({x = 1}, { + __call = function(t, ...) + this_is_local_variable = t.x end }) @@ -315,6 +314,73 @@ describe('luaeval()', function() ]]) end) + it('should handle being called from a timer once.', function() + eq(3, exec_lua [[ + local this_is_local_variable = false + local callable_table = setmetatable({5, 4, 3, 2, 1}, { + __call = function(t, ...) this_is_local_variable = t[3] end + }) + + vim.fn.timer_start(5, callable_table) + vim.wait(1000, function() + return this_is_local_variable + end) + + return this_is_local_variable + ]]) + end) + + it('should call functions once with __call metamethod', function() + eq(true, exec_lua [[ + local this_is_local_variable = false + local callable_table = setmetatable({a = true, b = false}, { + __call = function(t, ...) this_is_local_variable = t.a end + }) + + assert(getmetatable(callable_table).__call) + vim.fn.call(callable_table, {}) + + return this_is_local_variable + ]]) + end) + + it('should work with lists using __call', function() + eq(3, exec_lua [[ + local this_is_local_variable = false + local mt = { + __call = function(t, ...) + this_is_local_variable = t[3] + end + } + local callable_table = setmetatable({5, 4, 3, 2, 1}, mt) + + -- Call it once... + vim.fn.timer_start(5, callable_table) + vim.wait(1000, function() + return this_is_local_variable + end) + + assert(this_is_local_variable) + this_is_local_variable = false + + vim.fn.timer_start(5, callable_table) + vim.wait(1000, function() + return this_is_local_variable + end) + + return this_is_local_variable + ]]) + end) + + it('should not work with tables not using __call', function() + eq({false, 'Vim:E921: Invalid callback argument'}, exec_lua [[ + local this_is_local_variable = false + local callable_table = setmetatable({x = 1}, {}) + + return {pcall(function() vim.fn.timer_start(5, callable_table) end)} + ]]) + end) + it('correctly converts containers with type_idx', function() eq(5, eval('type(luaeval("{[vim.type_idx]=vim.types.float, [vim.val_idx]=0}"))')) eq(4, eval([[type(luaeval('{[vim.type_idx]=vim.types.dictionary}'))]])) -- cgit From 398201cfabaef47aa93c8a485336a00ef075ad3e Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Sun, 19 Jul 2020 17:16:48 -0400 Subject: lua: Fix crash on unprotected lua errors (#12658) Can be reproduced with a script like this: -- in some lua file vim.fn.timer_start(10, function() error("uh....") end) -- will cause neovim to crash with the following error. PANIC: unprotected error in call to Lua API (nlua_CFunction_func_call failed.) After this, it will instead print the error message from the top of the stack, like so. tmp/error_nvim.lua:10: uh... Also added an example test. Previously this test caused the embedded nvim to panic. --- test/functional/lua/luaeval_spec.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'test/functional/lua/luaeval_spec.lua') diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua index 964ea4561e..75966393b1 100644 --- a/test/functional/lua/luaeval_spec.lua +++ b/test/functional/lua/luaeval_spec.lua @@ -255,6 +255,18 @@ describe('luaeval()', function() ]]) end) + it('can handle functions with errors', function() + eq(true, exec_lua [[ + vim.fn.timer_start(10, function() + error("dead function") + end) + + vim.wait(1000, function() return false end) + + return true + ]]) + end) + it('should handle passing functions around', function() command [[ function VimCanCallLuaCallbacks(Concat, Cb) -- cgit