aboutsummaryrefslogtreecommitdiff
path: root/test/functional/lua
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional/lua')
-rw-r--r--test/functional/lua/api_spec.lua32
-rw-r--r--test/functional/lua/buffer_updates_spec.lua17
-rw-r--r--test/functional/lua/commands_spec.lua99
-rw-r--r--test/functional/lua/luaeval_spec.lua308
-rw-r--r--test/functional/lua/overrides_spec.lua34
-rw-r--r--test/functional/lua/treesitter_spec.lua512
-rw-r--r--test/functional/lua/uri_spec.lua150
-rw-r--r--test/functional/lua/utility_functions_spec.lua291
-rw-r--r--test/functional/lua/vim_spec.lua1235
9 files changed, 2283 insertions, 395 deletions
diff --git a/test/functional/lua/api_spec.lua b/test/functional/lua/api_spec.lua
index b1dc5c07fd..896554f7a3 100644
--- a/test/functional/lua/api_spec.lua
+++ b/test/functional/lua/api_spec.lua
@@ -39,7 +39,7 @@ describe('luaeval(vim.api.…)', function()
eq({false, 'Argument "pos" must be a [row, col] array'},
funcs.luaeval('{pcall(vim.api.nvim_win_set_cursor, 0, {1, 2, 3})}'))
-- Used to produce a memory leak due to a bug in nvim_win_set_cursor
- eq({false, 'Invalid window id'},
+ eq({false, 'Invalid window id: -1'},
funcs.luaeval('{pcall(vim.api.nvim_win_set_cursor, -1, {1, 2, 3})}'))
end)
@@ -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 "<VimL compiled 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 "<VimL compiled 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 "<VimL compiled 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 "<VimL compiled 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 "<VimL compiled 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 "<VimL compiled 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 "<VimL compiled 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 "<VimL compiled 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 "<VimL compiled 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 "<VimL compiled 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 "<VimL compiled 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 "<VimL compiled 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 "<VimL compiled 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 "<VimL compiled 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 "<VimL compiled 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/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua
index 990cb97fec..77f8189bb9 100644
--- a/test/functional/lua/buffer_updates_spec.lua
+++ b/test/functional/lua/buffer_updates_spec.lua
@@ -98,7 +98,7 @@ describe('lua: buffer event callbacks', function()
command('undo')
-- plugins can opt in to receive changedtick events, or choose
- -- to only recieve actual changes.
+ -- to only receive actual changes.
check_events({{ "test1", "lines", 1, tick, 3, 4, 5, 13 },
{ "test2", "lines", 1, tick, 3, 4, 5, 13 },
{ "test2", "changedtick", 1, tick+1 } })
@@ -111,7 +111,7 @@ describe('lua: buffer event callbacks', function()
tick = tick + 1
-- plugins can opt in to receive changedtick events, or choose
- -- to only recieve actual changes.
+ -- to only receive actual changes.
check_events({{ "test1", "lines", 1, tick, 6, 7, 9, 16 },
{ "test2", "lines", 1, tick, 6, 7, 9, 16 }})
@@ -203,4 +203,17 @@ describe('lua: buffer event callbacks', function()
{ "test1", "lines", 1, tick+1, 5, 6, 5, 27, 20, 20 }}, exec_lua("return get_events(...)" ))
end)
+ it('has valid cursor position while shifting', function()
+ meths.buf_set_lines(0, 0, -1, true, {'line1'})
+ exec_lua([[
+ vim.api.nvim_buf_attach(0, false, {
+ on_lines = function()
+ vim.api.nvim_set_var('listener_cursor_line', vim.api.nvim_win_get_cursor(0)[1])
+ end,
+ })
+ ]])
+ feed('>>')
+ eq(1, meths.get_var('listener_cursor_line'))
+ end)
+
end)
diff --git a/test/functional/lua/commands_spec.lua b/test/functional/lua/commands_spec.lua
index 26dcbe0534..cbc3aee557 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 "<VimL compiled string>"]:1: unexpected symbol near ')']],
- exc_exec('lua ()'))
- eq([[Vim(lua):E5105: Error while calling lua chunk: [string "<VimL compiled 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 "<VimL compiled string>"]:1: Invalid buffer id]],
+ eq([[Vim(lua):E5108: Error executing lua [string ":lua"]:1: Invalid buffer id: -10]],
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 "<VimL compiled string>"]:1: unfinished string near \'<eof>\'', 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 \'<eof>\'', 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")<cr>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {2: }|
- {3:E5105: Error while calling lua chunk: [string "<Vi}|
- {3:mL compiled 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('<cr>')
- screen:expect([[
- ^ |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- |
- ]])
- eq('E5105: Error while calling lua chunk: [string "<VimL compiled 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 "<VimL compiled 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<cr>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {2: }|
- {3:E5105: Error while calling lua chunk: [string "<Vi}|
- {3:mL compiled 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 "<VimL compiled 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 "<VimL compiled 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 "<VimL compiled string>"]:1: unfinished string near \'<eof>\'', redir_exec(('luado return "%s'):format(s)))
+ eq('\nE5109: Error loading lua: [string ":luado"]:1: unfinished string near \'<eof>\'', 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..75966393b1 100644
--- a/test/functional/lua/luaeval_spec.lua
+++ b/test/functional/lua/luaeval_spec.lua
@@ -1,13 +1,16 @@
-- 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
@@ -184,23 +187,210 @@ 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 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}")'))
- 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('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)
+ 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)
+
+ it('should work with metatables using __call', function()
+ eq(1, exec_lua [[
+ local this_is_local_variable = false
+ local callable_table = setmetatable({x = 1}, {
+ __call = function(t, ...)
+ this_is_local_variable = t.x
+ 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 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()
@@ -237,19 +427,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 "<VimL compiled 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 "<VimL compiled 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 "<VimL compiled 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 \"<nvim>\"]: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 \"<nvim>\"]: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<c-x><c-o>')
+ 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..1bccc02847 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: <Unknown error: lua_tolstring returned NULL for tostring result>',
+ eq('\nE5108: Error executing lua E5114: Error while converting print argument #2: <Unknown error: lua_tolstring returned NULL for tostring result>',
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('<C-c>')
- 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 "<VimL }|
- {E:compiled 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('<C-l>: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 "<VimL }|
- {E:compiled 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()
@@ -298,14 +299,11 @@ describe('package.path/package.cpath', function()
end
return new_paths
end
- local function execute_lua(cmd, ...)
- return meths.execute_lua(cmd, {...})
- end
local function eval_lua(expr, ...)
- return meths.execute_lua('return ' .. expr, {...})
+ return meths.exec_lua('return '..expr, {...})
end
local function set_path(which, value)
- return execute_lua('package[select(1, ...)] = select(2, ...)', which, value)
+ return exec_lua('package[select(1, ...)] = select(2, ...)', which, value)
end
it('contains directories from &runtimepath on first invocation', function()
diff --git a/test/functional/lua/treesitter_spec.lua b/test/functional/lua/treesitter_spec.lua
new file mode 100644
index 0000000000..aa3d55b06d
--- /dev/null
+++ b/test/functional/lua/treesitter_spec.lua
@@ -0,0 +1,512 @@
+-- Test suite for testing interactions with API bindings
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+
+local clear = helpers.clear
+local eq = helpers.eq
+local insert = helpers.insert
+local exec_lua = helpers.exec_lua
+local feed = helpers.feed
+local pcall_err = helpers.pcall_err
+local matches = helpers.matches
+
+before_each(clear)
+
+describe('treesitter API', function()
+ -- error tests not requiring a parser library
+ it('handles missing language', function()
+ eq("Error executing lua: .../treesitter.lua: no parser for 'borklang' language",
+ pcall_err(exec_lua, "parser = vim.treesitter.create_parser(0, 'borklang')"))
+
+ -- actual message depends on platform
+ matches("Error executing lua: Failed to load parser: uv_dlopen: .+",
+ pcall_err(exec_lua, "parser = vim.treesitter.require_language('borklang', 'borkbork.so')"))
+
+ eq("Error executing lua: .../treesitter.lua: no parser for 'borklang' language",
+ pcall_err(exec_lua, "parser = vim.treesitter.inspect_language('borklang')"))
+ end)
+
+end)
+
+describe('treesitter API with C parser', function()
+ local function check_parser()
+ local status, msg = unpack(exec_lua([[ return {pcall(vim.treesitter.require_language, 'c')} ]]))
+ if not status then
+ if helpers.isCI() then
+ error("treesitter C parser not found, required on CI: " .. msg)
+ else
+ pending('no C parser, skipping')
+ end
+ end
+ return status
+ end
+
+ it('parses buffer', function()
+ if not check_parser() then return end
+
+ insert([[
+ int main() {
+ int x = 3;
+ }]])
+
+ exec_lua([[
+ parser = vim.treesitter.get_parser(0, "c")
+ tree = parser:parse()
+ root = tree:root()
+ lang = vim.treesitter.inspect_language('c')
+ ]])
+
+ eq("<tree>", exec_lua("return tostring(tree)"))
+ eq("<node translation_unit>", exec_lua("return tostring(root)"))
+ eq({0,0,3,0}, exec_lua("return {root:range()}"))
+
+ eq(1, exec_lua("return root:child_count()"))
+ exec_lua("child = root:child(0)")
+ eq("<node function_definition>", exec_lua("return tostring(child)"))
+ eq({0,0,2,1}, exec_lua("return {child:range()}"))
+
+ eq("function_definition", exec_lua("return child:type()"))
+ eq(true, exec_lua("return child:named()"))
+ eq("number", type(exec_lua("return child:symbol()")))
+ eq({'function_definition', true}, exec_lua("return lang.symbols[child:symbol()]"))
+
+ exec_lua("anon = root:descendant_for_range(0,8,0,9)")
+ eq("(", exec_lua("return anon:type()"))
+ eq(false, exec_lua("return anon:named()"))
+ eq("number", type(exec_lua("return anon:symbol()")))
+ eq({'(', false}, exec_lua("return lang.symbols[anon:symbol()]"))
+
+ exec_lua("descendant = root:descendant_for_range(1,2,1,12)")
+ eq("<node declaration>", exec_lua("return tostring(descendant)"))
+ eq({1,2,1,12}, exec_lua("return {descendant:range()}"))
+ eq("(declaration type: (primitive_type) declarator: (init_declarator declarator: (identifier) value: (number_literal)))", exec_lua("return descendant:sexpr()"))
+
+ eq(true, exec_lua("return child == child"))
+ -- separate lua object, but represents same node
+ eq(true, exec_lua("return child == root:child(0)"))
+ eq(false, exec_lua("return child == descendant2"))
+ eq(false, exec_lua("return child == nil"))
+ eq(false, exec_lua("return child == tree"))
+
+ feed("2G7|ay")
+ exec_lua([[
+ tree2 = parser:parse()
+ root2 = tree2:root()
+ descendant2 = root2:descendant_for_range(1,2,1,13)
+ ]])
+ eq(false, exec_lua("return tree2 == tree1"))
+ eq(false, exec_lua("return root2 == root"))
+ eq("<node declaration>", exec_lua("return tostring(descendant2)"))
+ eq({1,2,1,13}, exec_lua("return {descendant2:range()}"))
+
+ -- orginal tree did not change
+ eq({1,2,1,12}, exec_lua("return {descendant:range()}"))
+
+ -- unchanged buffer: return the same tree
+ eq(true, exec_lua("return parser:parse() == tree2"))
+ end)
+
+ local test_text = [[
+void ui_refresh(void)
+{
+ int width = INT_MAX, height = INT_MAX;
+ bool ext_widgets[kUIExtCount];
+ for (UIExtension i = 0; (int)i < kUIExtCount; i++) {
+ ext_widgets[i] = true;
+ }
+
+ bool inclusive = ui_override();
+ for (size_t i = 0; i < ui_count; i++) {
+ UI *ui = uis[i];
+ width = MIN(ui->width, width);
+ height = MIN(ui->height, height);
+ foo = BAR(ui->bazaar, bazaar);
+ for (UIExtension j = 0; (int)j < kUIExtCount; j++) {
+ ext_widgets[j] &= (ui->ui_ext[j] || inclusive);
+ }
+ }
+}]]
+
+ local query = [[
+ ((call_expression function: (identifier) @minfunc (argument_list (identifier) @min_id)) (eq? @minfunc "MIN"))
+ "for" @keyword
+ (primitive_type) @type
+ (field_expression argument: (identifier) @fieldarg)
+ ]]
+
+ it('support query and iter by capture', function()
+ if not check_parser() then return end
+
+ insert(test_text)
+
+ local res = exec_lua([[
+ cquery = vim.treesitter.parse_query("c", ...)
+ parser = vim.treesitter.get_parser(0, "c")
+ tree = parser:parse()
+ res = {}
+ for cid, node in cquery:iter_captures(tree:root(), 0, 7, 14) do
+ -- can't transmit node over RPC. just check the name and range
+ table.insert(res, {cquery.captures[cid], node:type(), node:range()})
+ end
+ return res
+ ]], query)
+
+ eq({
+ { "type", "primitive_type", 8, 2, 8, 6 },
+ { "keyword", "for", 9, 2, 9, 5 },
+ { "type", "primitive_type", 9, 7, 9, 13 },
+ { "minfunc", "identifier", 11, 12, 11, 15 },
+ { "fieldarg", "identifier", 11, 16, 11, 18 },
+ { "min_id", "identifier", 11, 27, 11, 32 },
+ { "minfunc", "identifier", 12, 13, 12, 16 },
+ { "fieldarg", "identifier", 12, 17, 12, 19 },
+ { "min_id", "identifier", 12, 29, 12, 35 },
+ { "fieldarg", "identifier", 13, 14, 13, 16 }
+ }, res)
+ end)
+
+ it('support query and iter by match', function()
+ if not check_parser() then return end
+
+ insert(test_text)
+
+ local res = exec_lua([[
+ cquery = vim.treesitter.parse_query("c", ...)
+ parser = vim.treesitter.get_parser(0, "c")
+ tree = parser:parse()
+ res = {}
+ for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14) do
+ -- can't transmit node over RPC. just check the name and range
+ local mrepr = {}
+ for cid,node in pairs(match) do
+ table.insert(mrepr, {cquery.captures[cid], node:type(), node:range()})
+ end
+ table.insert(res, {pattern, mrepr})
+ end
+ return res
+ ]], query)
+
+ eq({
+ { 3, { { "type", "primitive_type", 8, 2, 8, 6 } } },
+ { 2, { { "keyword", "for", 9, 2, 9, 5 } } },
+ { 3, { { "type", "primitive_type", 9, 7, 9, 13 } } },
+ { 4, { { "fieldarg", "identifier", 11, 16, 11, 18 } } },
+ { 1, { { "minfunc", "identifier", 11, 12, 11, 15 }, { "min_id", "identifier", 11, 27, 11, 32 } } },
+ { 4, { { "fieldarg", "identifier", 12, 17, 12, 19 } } },
+ { 1, { { "minfunc", "identifier", 12, 13, 12, 16 }, { "min_id", "identifier", 12, 29, 12, 35 } } },
+ { 4, { { "fieldarg", "identifier", 13, 14, 13, 16 } } }
+ }, res)
+ end)
+
+ it('supports highlighting', function()
+ if not check_parser() then return end
+
+ local hl_text = [[
+/// Schedule Lua callback on main loop's event queue
+static int nlua_schedule(lua_State *const lstate)
+{
+ if (lua_type(lstate, 1) != LUA_TFUNCTION
+ || lstate != lstate) {
+ lua_pushliteral(lstate, "vim.schedule: expected function");
+ return lua_error(lstate);
+ }
+
+ LuaRef cb = nlua_ref(lstate, 1);
+
+ multiqueue_put(main_loop.events, nlua_schedule_event,
+ 1, (void *)(ptrdiff_t)cb);
+ return 0;
+}]]
+
+ local hl_query = [[
+(ERROR) @ErrorMsg
+
+"if" @keyword
+"else" @keyword
+"for" @keyword
+"return" @keyword
+
+"const" @type
+"static" @type
+"struct" @type
+"enum" @type
+"extern" @type
+
+(string_literal) @string
+
+(number_literal) @number
+(char_literal) @string
+
+(type_identifier) @type
+((type_identifier) @Special (#eq? @Special "LuaRef"))
+
+(primitive_type) @type
+(sized_type_specifier) @type
+
+; defaults to very magic syntax, for best compatibility
+((identifier) @Identifier (#match? @Identifier "^l(u)a_"))
+; still support \M etc prefixes
+((identifier) @Constant (#match? @Constant "\M^\[A-Z_]\+$"))
+
+((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right) (#eq? @WarningMsg.left @WarningMsg.right))
+
+(comment) @comment
+]]
+
+ local screen = Screen.new(65, 18)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [1] = {bold = true, foreground = Screen.colors.Blue1},
+ [2] = {foreground = Screen.colors.Blue1},
+ [3] = {bold = true, foreground = Screen.colors.SeaGreen4},
+ [4] = {bold = true, foreground = Screen.colors.Brown},
+ [5] = {foreground = Screen.colors.Magenta},
+ [6] = {foreground = Screen.colors.Red},
+ [7] = {bold = true, foreground = Screen.colors.SlateBlue},
+ [8] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ [9] = {foreground = Screen.colors.Magenta, background = Screen.colors.Red},
+ [10] = {foreground = Screen.colors.Red, background = Screen.colors.Red},
+ [11] = {foreground = Screen.colors.Cyan4},
+ })
+
+ insert(hl_text)
+ screen:expect{grid=[[
+ /// Schedule Lua callback on main loop's event queue |
+ static int nlua_schedule(lua_State *const lstate) |
+ { |
+ if (lua_type(lstate, 1) != LUA_TFUNCTION |
+ || lstate != lstate) { |
+ lua_pushliteral(lstate, "vim.schedule: expected function"); |
+ return lua_error(lstate); |
+ } |
+ |
+ LuaRef cb = nlua_ref(lstate, 1); |
+ |
+ multiqueue_put(main_loop.events, nlua_schedule_event, |
+ 1, (void *)(ptrdiff_t)cb); |
+ return 0; |
+ ^} |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ exec_lua([[
+ local TSHighlighter = vim.treesitter.TSHighlighter
+ local query = ...
+ test_hl = TSHighlighter.new(query, 0, "c")
+ ]], hl_query)
+ screen:expect{grid=[[
+ {2:/// Schedule Lua callback on main loop's event queue} |
+ {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) |
+ { |
+ {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} |
+ || {6:lstate} != {6:lstate}) { |
+ {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); |
+ {4:return} {11:lua_error}(lstate); |
+ } |
+ |
+ {7:LuaRef} cb = nlua_ref(lstate, {5:1}); |
+ |
+ multiqueue_put(main_loop.events, nlua_schedule_event, |
+ {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
+ {4:return} {5:0}; |
+ ^} |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed('7Go*/<esc>')
+ screen:expect{grid=[[
+ {2:/// Schedule Lua callback on main loop's event queue} |
+ {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) |
+ { |
+ {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} |
+ || {6:lstate} != {6:lstate}) { |
+ {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); |
+ {4:return} {11:lua_error}(lstate); |
+ {8:*^/} |
+ } |
+ |
+ {7:LuaRef} cb = nlua_ref(lstate, {5:1}); |
+ |
+ multiqueue_put(main_loop.events, nlua_schedule_event, |
+ {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
+ {4:return} {5:0}; |
+ } |
+ {1:~ }|
+ |
+ ]]}
+
+ feed('3Go/*<esc>')
+ screen:expect{grid=[[
+ {2:/// Schedule Lua callback on main loop's event queue} |
+ {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) |
+ { |
+ {2:/^*} |
+ {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} |
+ {2: || lstate != lstate) {} |
+ {2: lua_pushliteral(lstate, "vim.schedule: expected function");} |
+ {2: return lua_error(lstate);} |
+ {2:*/} |
+ } |
+ |
+ {7:LuaRef} cb = nlua_ref(lstate, {5:1}); |
+ |
+ multiqueue_put(main_loop.events, nlua_schedule_event, |
+ {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
+ {4:return} {5:0}; |
+ {8:}} |
+ |
+ ]]}
+
+ feed("gg$")
+ feed("~")
+ screen:expect{grid=[[
+ {2:/// Schedule Lua callback on main loop's event queu^E} |
+ {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) |
+ { |
+ {2:/*} |
+ {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} |
+ {2: || lstate != lstate) {} |
+ {2: lua_pushliteral(lstate, "vim.schedule: expected function");} |
+ {2: return lua_error(lstate);} |
+ {2:*/} |
+ } |
+ |
+ {7:LuaRef} cb = nlua_ref(lstate, {5:1}); |
+ |
+ multiqueue_put(main_loop.events, nlua_schedule_event, |
+ {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
+ {4:return} {5:0}; |
+ {8:}} |
+ |
+ ]]}
+
+
+ feed("re")
+ screen:expect{grid=[[
+ {2:/// Schedule Lua callback on main loop's event queu^e} |
+ {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) |
+ { |
+ {2:/*} |
+ {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} |
+ {2: || lstate != lstate) {} |
+ {2: lua_pushliteral(lstate, "vim.schedule: expected function");} |
+ {2: return lua_error(lstate);} |
+ {2:*/} |
+ } |
+ |
+ {7:LuaRef} cb = nlua_ref(lstate, {5:1}); |
+ |
+ multiqueue_put(main_loop.events, nlua_schedule_event, |
+ {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
+ {4:return} {5:0}; |
+ {8:}} |
+ |
+ ]]}
+ end)
+
+ it('inspects language', function()
+ if not check_parser() then return end
+
+ local keys, fields, symbols = unpack(exec_lua([[
+ local lang = vim.treesitter.inspect_language('c')
+ local keys, symbols = {}, {}
+ for k,_ in pairs(lang) do
+ keys[k] = true
+ end
+
+ -- symbols array can have "holes" and is thus not a valid msgpack array
+ -- but we don't care about the numbers here (checked in the parser test)
+ for _, v in pairs(lang.symbols) do
+ table.insert(symbols, v)
+ end
+ return {keys, lang.fields, symbols}
+ ]]))
+
+ eq({fields=true, symbols=true}, keys)
+
+ local fset = {}
+ for _,f in pairs(fields) do
+ eq("string", type(f))
+ fset[f] = true
+ end
+ eq(true, fset["directive"])
+ eq(true, fset["initializer"])
+
+ local has_named, has_anonymous
+ for _,s in pairs(symbols) do
+ eq("string", type(s[1]))
+ eq("boolean", type(s[2]))
+ if s[1] == "for_statement" and s[2] == true then
+ has_named = true
+ elseif s[1] == "|=" and s[2] == false then
+ has_anonymous = true
+ end
+ end
+ eq({true,true}, {has_named,has_anonymous})
+ end)
+ it('allows to set simple ranges', function()
+ if not check_parser() then return end
+
+ insert(test_text)
+
+ local res = exec_lua([[
+ parser = vim.treesitter.get_parser(0, "c")
+ return { parser:parse():root():range() }
+ ]])
+
+ eq({0, 0, 19, 0}, res)
+
+ -- The following sets the included ranges for the current parser
+ -- As stated here, this only includes the function (thus the whole buffer, without the last line)
+ local res2 = exec_lua([[
+ local root = parser:parse():root()
+ parser:set_included_ranges({root:child(0)})
+ parser.valid = false
+ return { parser:parse():root():range() }
+ ]])
+
+ eq({0, 0, 18, 1}, res2)
+ end)
+ it("allows to set complex ranges", function()
+ if not check_parser() then return end
+
+ insert(test_text)
+
+
+ local res = exec_lua([[
+ parser = vim.treesitter.get_parser(0, "c")
+ query = vim.treesitter.parse_query("c", "(declaration) @decl")
+
+ local nodes = {}
+ for _, node in query:iter_captures(parser:parse():root(), 0, 0, 19) do
+ table.insert(nodes, node)
+ end
+
+ parser:set_included_ranges(nodes)
+
+ local root = parser:parse():root()
+
+ local res = {}
+ for i=0,(root:named_child_count() - 1) do
+ table.insert(res, { root:named_child(i):range() })
+ end
+ return res
+ ]])
+
+ eq({
+ { 2, 2, 2, 40 },
+ { 3, 3, 3, 32 },
+ { 4, 7, 4, 8 },
+ { 4, 8, 4, 25 },
+ { 8, 2, 8, 6 },
+ { 8, 7, 8, 33 },
+ { 9, 8, 9, 20 },
+ { 10, 4, 10, 5 },
+ { 10, 5, 10, 20 },
+ { 14, 9, 14, 27 } }, res)
+ end)
+end)
diff --git a/test/functional/lua/uri_spec.lua b/test/functional/lua/uri_spec.lua
new file mode 100644
index 0000000000..f782769935
--- /dev/null
+++ b/test/functional/lua/uri_spec.lua
@@ -0,0 +1,150 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local exec_lua = helpers.exec_lua
+local eq = helpers.eq
+
+describe('URI methods', function()
+ before_each(function()
+ clear()
+ end)
+
+ describe('file path to uri', function()
+ describe('encode Unix file path', function()
+ it('file path includes only ascii charactors', function()
+ exec_lua("filepath = '/Foo/Bar/Baz.txt'")
+
+ eq('file:///Foo/Bar/Baz.txt', exec_lua("return vim.uri_from_fname(filepath)"))
+ end)
+
+ it('file path including white space', function()
+ exec_lua("filepath = '/Foo /Bar/Baz.txt'")
+
+ eq('file:///Foo%20/Bar/Baz.txt', exec_lua("return vim.uri_from_fname(filepath)"))
+ end)
+
+ it('file path including Unicode charactors', function()
+ exec_lua("filepath = '/xy/åäö/ɧ/汉语/↥/🤦/🦄/å/بِيَّ.txt'")
+
+ -- The URI encoding should be case-insensitive
+ eq('file:///xy/%c3%a5%c3%a4%c3%b6/%c9%a7/%e6%b1%89%e8%af%ad/%e2%86%a5/%f0%9f%a4%a6/%f0%9f%a6%84/a%cc%8a/%d8%a8%d9%90%d9%8a%d9%8e%d9%91.txt', exec_lua("return vim.uri_from_fname(filepath)"))
+ end)
+ end)
+
+ describe('encode Windows filepath', function()
+ it('file path includes only ascii charactors', function()
+ exec_lua([[filepath = 'C:\\Foo\\Bar\\Baz.txt']])
+
+ eq('file:///C:/Foo/Bar/Baz.txt', exec_lua("return vim.uri_from_fname(filepath)"))
+ end)
+
+ it('file path including white space', function()
+ exec_lua([[filepath = 'C:\\Foo \\Bar\\Baz.txt']])
+
+ eq('file:///C:/Foo%20/Bar/Baz.txt', exec_lua("return vim.uri_from_fname(filepath)"))
+ end)
+
+ it('file path including Unicode charactors', function()
+ exec_lua([[filepath = 'C:\\xy\\åäö\\ɧ\\汉语\\↥\\🤦\\🦄\\å\\بِيَّ.txt']])
+
+ eq('file:///C:/xy/%c3%a5%c3%a4%c3%b6/%c9%a7/%e6%b1%89%e8%af%ad/%e2%86%a5/%f0%9f%a4%a6/%f0%9f%a6%84/a%cc%8a/%d8%a8%d9%90%d9%8a%d9%8e%d9%91.txt', exec_lua("return vim.uri_from_fname(filepath)"))
+ end)
+ end)
+ end)
+
+ describe('uri to filepath', function()
+ describe('decode Unix file path', function()
+ it('file path includes only ascii charactors', function()
+ exec_lua("uri = 'file:///Foo/Bar/Baz.txt'")
+
+ eq('/Foo/Bar/Baz.txt', exec_lua("return vim.uri_to_fname(uri)"))
+ end)
+
+ it('file path including white space', function()
+ exec_lua("uri = 'file:///Foo%20/Bar/Baz.txt'")
+
+ eq('/Foo /Bar/Baz.txt', exec_lua("return vim.uri_to_fname(uri)"))
+ end)
+
+ it('file path including Unicode charactors', function()
+ local test_case = [[
+ local uri = 'file:///xy/%C3%A5%C3%A4%C3%B6/%C9%A7/%E6%B1%89%E8%AF%AD/%E2%86%A5/%F0%9F%A4%A6/%F0%9F%A6%84/a%CC%8A/%D8%A8%D9%90%D9%8A%D9%8E%D9%91.txt'
+ return vim.uri_to_fname(uri)
+ ]]
+
+ eq('/xy/åäö/ɧ/汉语/↥/🤦/🦄/å/بِيَّ.txt', exec_lua(test_case))
+ end)
+ end)
+
+ describe('decode Windows filepath', function()
+ it('file path includes only ascii charactors', function()
+ local test_case = [[
+ local uri = 'file:///C:/Foo/Bar/Baz.txt'
+ return vim.uri_to_fname(uri)
+ ]]
+
+ eq('C:\\Foo\\Bar\\Baz.txt', exec_lua(test_case))
+ end)
+
+ it('file path includes only ascii charactors with encoded colon character', function()
+ local test_case = [[
+ local uri = 'file:///C%3A/Foo/Bar/Baz.txt'
+ return vim.uri_to_fname(uri)
+ ]]
+
+ eq('C:\\Foo\\Bar\\Baz.txt', exec_lua(test_case))
+ end)
+
+ it('file path including white space', function()
+ local test_case = [[
+ local uri = 'file:///C:/Foo%20/Bar/Baz.txt'
+ return vim.uri_to_fname(uri)
+ ]]
+
+ eq('C:\\Foo \\Bar\\Baz.txt', exec_lua(test_case))
+ end)
+
+ it('file path including Unicode charactors', function()
+ local test_case = [[
+ local uri = 'file:///C:/xy/%C3%A5%C3%A4%C3%B6/%C9%A7/%E6%B1%89%E8%AF%AD/%E2%86%A5/%F0%9F%A4%A6/%F0%9F%A6%84/a%CC%8A/%D8%A8%D9%90%D9%8A%D9%8E%D9%91.txt'
+ return vim.uri_to_fname(uri)
+ ]]
+
+ eq('C:\\xy\\åäö\\ɧ\\汉语\\↥\\🤦\\🦄\\å\\بِيَّ.txt', exec_lua(test_case))
+ end)
+ end)
+
+ describe('decode non-file URI', function()
+ it('uri_to_fname returns non-file URI unchanged', function()
+ eq('jdt1.23+x-z://content/%5C/', exec_lua [[
+ return vim.uri_to_fname('jdt1.23+x-z://content/%5C/')
+ ]])
+ end)
+
+ it('uri_to_fname returns non-file upper-case scheme URI unchanged', function()
+ eq('JDT://content/%5C/', exec_lua [[
+ return vim.uri_to_fname('JDT://content/%5C/')
+ ]])
+ end)
+ end)
+
+ describe('decode URI without scheme', function()
+ it('fails because URI must have a scheme', function()
+ eq(false, exec_lua [[
+ return pcall(vim.uri_to_fname, 'not_an_uri.txt')
+ ]])
+ end)
+ end)
+
+ end)
+
+ describe('uri to bufnr', function()
+ it('uri_to_bufnr & uri_from_bufnr returns original uri for non-file uris', function()
+ local uri = 'jdt://contents/java.base/java.util/List.class?=sql/%5C/home%5C/user%5C/.jabba%5C/jdk%5C/openjdk%5C@1.14.0%5C/lib%5C/jrt-fs.jar%60java.base=/javadoc_location=/https:%5C/%5C/docs.oracle.com%5C/en%5C/java%5C/javase%5C/14%5C/docs%5C/api%5C/=/%3Cjava.util(List.class'
+ local test_case = string.format([[
+ local uri = '%s'
+ return vim.uri_from_bufnr(vim.uri_to_bufnr(uri))
+ ]], uri)
+ eq(uri, exec_lua(test_case))
+ end)
+ end)
+end)
diff --git a/test/functional/lua/utility_functions_spec.lua b/test/functional/lua/utility_functions_spec.lua
deleted file mode 100644
index ea2b1fc8a9..0000000000
--- a/test/functional/lua/utility_functions_spec.lua
+++ /dev/null
@@ -1,291 +0,0 @@
--- Test suite for testing interactions with API bindings
-local helpers = require('test.functional.helpers')(after_each)
-local Screen = require('test.functional.ui.screen')
-
-local funcs = helpers.funcs
-local clear = helpers.clear
-local eq = helpers.eq
-local eval = helpers.eval
-local feed = helpers.feed
-local pcall_err = helpers.pcall_err
-local exec_lua = helpers.exec_lua
-
-before_each(clear)
-
-describe('lua stdlib', function()
- -- İ: `tolower("İ")` is `i` which has length 1 while `İ` itself has
- -- length 2 (in bytes).
- -- Ⱥ: `tolower("Ⱥ")` is `ⱥ` which has length 2 while `Ⱥ` itself has
- -- length 3 (in bytes).
- --
- -- Note: 'i' !=? 'İ' and 'ⱥ' !=? 'Ⱥ' on some systems.
- -- Note: Built-in Nvim comparison (on systems lacking `strcasecmp`) works
- -- only on ASCII characters.
- it('vim.stricmp', function()
- eq(0, funcs.luaeval('vim.stricmp("a", "A")'))
- eq(0, funcs.luaeval('vim.stricmp("A", "a")'))
- eq(0, funcs.luaeval('vim.stricmp("a", "a")'))
- eq(0, funcs.luaeval('vim.stricmp("A", "A")'))
-
- eq(0, funcs.luaeval('vim.stricmp("", "")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0", "\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0\\0", "\\0\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0", "\\0\\0\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0A", "\\0\\0\\0a")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0A")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0a")'))
-
- eq(0, funcs.luaeval('vim.stricmp("a\\0", "A\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("A\\0", "a\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("a\\0", "a\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("A\\0", "A\\0")'))
-
- eq(0, funcs.luaeval('vim.stricmp("\\0a", "\\0A")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0A", "\\0a")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0a", "\\0a")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0A", "\\0A")'))
-
- eq(0, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0A\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0a\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0a\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0A\\0")'))
-
- eq(-1, funcs.luaeval('vim.stricmp("a", "B")'))
- eq(-1, funcs.luaeval('vim.stricmp("A", "b")'))
- eq(-1, funcs.luaeval('vim.stricmp("a", "b")'))
- eq(-1, funcs.luaeval('vim.stricmp("A", "B")'))
-
- eq(-1, funcs.luaeval('vim.stricmp("", "\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0", "\\0\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0\\0", "\\0\\0\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0A", "\\0\\0\\0b")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0B")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0b")'))
-
- eq(-1, funcs.luaeval('vim.stricmp("a\\0", "B\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("A\\0", "b\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("a\\0", "b\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("A\\0", "B\\0")'))
-
- eq(-1, funcs.luaeval('vim.stricmp("\\0a", "\\0B")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0A", "\\0b")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0a", "\\0b")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0A", "\\0B")'))
-
- eq(-1, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0B\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0b\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0b\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0B\\0")'))
-
- eq(1, funcs.luaeval('vim.stricmp("c", "B")'))
- eq(1, funcs.luaeval('vim.stricmp("C", "b")'))
- eq(1, funcs.luaeval('vim.stricmp("c", "b")'))
- eq(1, funcs.luaeval('vim.stricmp("C", "B")'))
-
- eq(1, funcs.luaeval('vim.stricmp("\\0", "")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0\\0", "\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0", "\\0\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0\\0", "\\0\\0\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0C", "\\0\\0\\0b")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0c", "\\0\\0\\0B")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0c", "\\0\\0\\0b")'))
-
- eq(1, funcs.luaeval('vim.stricmp("c\\0", "B\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("C\\0", "b\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("c\\0", "b\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("C\\0", "B\\0")'))
-
- eq(1, funcs.luaeval('vim.stricmp("c\\0", "B")'))
- eq(1, funcs.luaeval('vim.stricmp("C\\0", "b")'))
- eq(1, funcs.luaeval('vim.stricmp("c\\0", "b")'))
- eq(1, funcs.luaeval('vim.stricmp("C\\0", "B")'))
-
- eq(1, funcs.luaeval('vim.stricmp("\\0c", "\\0B")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0C", "\\0b")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0c", "\\0b")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0C", "\\0B")'))
-
- eq(1, funcs.luaeval('vim.stricmp("\\0c\\0", "\\0B\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0b\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0c\\0", "\\0b\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0B\\0")'))
- end)
-
- it("vim.str_utfindex/str_byteindex", function()
- exec_lua([[_G.test_text = "xy åäö ɧ 汉语 ↥ 🤦x🦄 å بِيَّ"]])
- local indicies32 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,29,33,34,35,37,38,40,42,44,46,48}
- local indicies16 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,28,29,33,33,34,35,37,38,40,42,44,46,48}
- for i,k in pairs(indicies32) do
- eq(k, exec_lua("return vim.str_byteindex(_G.test_text, ...)", i), i)
- end
- for i,k in pairs(indicies16) do
- eq(k, exec_lua("return vim.str_byteindex(_G.test_text, ..., true)", i), i)
- end
- local i32, i16 = 0, 0
- for k = 0,48 do
- if indicies32[i32] < k then
- i32 = i32 + 1
- end
- if indicies16[i16] < k then
- i16 = i16 + 1
- if indicies16[i16+1] == indicies16[i16] then
- i16 = i16 + 1
- end
- end
- eq({i32, i16}, exec_lua("return {vim.str_utfindex(_G.test_text, ...)}", k), k)
- end
- end)
-
- it("vim.schedule", function()
- exec_lua([[
- test_table = {}
- vim.schedule(function()
- table.insert(test_table, "xx")
- end)
- table.insert(test_table, "yy")
- ]])
- eq({"yy","xx"}, exec_lua("return test_table"))
-
- -- type checked args
- eq('Error executing lua: vim.schedule: expected function',
- pcall_err(exec_lua, "vim.schedule('stringly')"))
-
- eq('Error executing lua: vim.schedule: expected function',
- pcall_err(exec_lua, "vim.schedule()"))
-
- exec_lua([[
- vim.schedule(function()
- error("big failure\nvery async")
- end)
- ]])
-
- feed("<cr>")
- eq('Error executing vim.schedule lua callback: [string "<nvim>"]:2: big failure\nvery async', eval("v:errmsg"))
-
- local screen = Screen.new(60,5)
- screen:set_default_attr_ids({
- [1] = {bold = true, foreground = Screen.colors.Blue1},
- [2] = {bold = true, reverse = true},
- [3] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
- [4] = {bold = true, foreground = Screen.colors.SeaGreen4},
- })
- screen:attach()
- screen:expect{grid=[[
- ^ |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- |
- ]]}
-
- -- nvim_command causes a vimL exception, check that it is properly caught
- -- and propagated as an error message in async contexts.. #10809
- exec_lua([[
- vim.schedule(function()
- vim.api.nvim_command(":echo 'err")
- end)
- ]])
- screen:expect{grid=[[
- |
- {2: }|
- {3:Error executing vim.schedule lua callback: [string "<nvim>"]}|
- {3::2: Vim(echo):E115: Missing quote: 'err} |
- {4:Press ENTER or type command to continue}^ |
- ]]}
- end)
-
- it("vim.split", function()
- local split = function(str, sep)
- return exec_lua('return vim.split(...)', str, sep)
- end
-
- local tests = {
- { "a,b", ",", false, { 'a', 'b' } },
- { ":aa::bb:", ":", false, { '', 'aa', '', 'bb', '' } },
- { "::ee::ff:", ":", false, { '', '', 'ee', '', 'ff', '' } },
- { "ab", ".", false, { '', '', '' } },
- { "a1b2c", "[0-9]", false, { 'a', 'b', 'c' } },
- { "xy", "", false, { 'x', 'y' } },
- { "here be dragons", " ", false, { "here", "be", "dragons"} },
- { "axaby", "ab?", false, { '', 'x', 'y' } },
- { "f v2v v3v w2w ", "([vw])2%1", false, { 'f ', ' v3v ', ' ' } },
- { "x*yz*oo*l", "*", true, { 'x', 'yz', 'oo', 'l' } },
- }
-
- for _, t in ipairs(tests) do
- eq(t[4], split(t[1], t[2], t[3]))
- end
-
- local loops = {
- { "abc", ".-" },
- }
-
- for _, t in ipairs(loops) do
- local status, err = pcall(split, t[1], t[2])
- eq(false, status)
- assert(string.match(err, "Infinite loop detected"))
- end
- end)
-
- it('vim.trim', function()
- local trim = function(s)
- return exec_lua('return vim.trim(...)', s)
- end
-
- local trims = {
- { " a", "a" },
- { " b ", "b" },
- { "\tc" , "c" },
- { "r\n", "r" },
- }
-
- for _, t in ipairs(trims) do
- assert(t[2], trim(t[1]))
- end
-
- local status, err = pcall(trim, 2)
- eq(false, status)
- assert(string.match(err, "Only strings can be trimmed"))
- end)
-
- it('vim.inspect', function()
- -- just make sure it basically works, it has its own test suite
- local inspect = function(t, opts)
- return exec_lua('return vim.inspect(...)', t, opts)
- end
-
- eq('2', inspect(2))
- eq('{+a = {+b = 1+}+}',
- inspect({ a = { b = 1 } }, { newline = '+', indent = '' }))
-
- -- special value vim.inspect.KEY works
- eq('{ KEY_a = "x", KEY_b = "y"}', exec_lua([[
- return vim.inspect({a="x", b="y"}, {newline = '', process = function(item, path)
- if path[#path] == vim.inspect.KEY then
- return 'KEY_'..item
- end
- return item
- end})
- ]]))
- end)
-
- it("vim.deepcopy", function()
- local is_dc = exec_lua([[
- local a = { x = { 1, 2 }, y = 5}
- local b = vim.deepcopy(a)
-
- local count = 0
- for _ in pairs(b) do count = count + 1 end
-
- return b.x[1] == 1 and b.x[2] == 2 and b.y == 5 and count == 2
- and tostring(a) ~= tostring(b)
- ]])
-
- assert(is_dc)
- end)
-
- it('vim.pesc', function()
- eq('foo%-bar', exec_lua([[return vim.pesc('foo-bar')]]))
- eq('foo%%%-bar', exec_lua([[return vim.pesc(vim.pesc('foo-bar'))]]))
- end)
-end)
diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua
new file mode 100644
index 0000000000..9b2697b3c2
--- /dev/null
+++ b/test/functional/lua/vim_spec.lua
@@ -0,0 +1,1235 @@
+-- Test suite for testing interactions with API bindings
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+
+local funcs = helpers.funcs
+local meths = helpers.meths
+local command = helpers.command
+local clear = helpers.clear
+local eq = helpers.eq
+local ok = helpers.ok
+local eval = helpers.eval
+local feed = helpers.feed
+local pcall_err = helpers.pcall_err
+local exec_lua = helpers.exec_lua
+local matches = helpers.matches
+local source = helpers.source
+local NIL = helpers.NIL
+local retry = helpers.retry
+
+before_each(clear)
+
+describe('lua stdlib', function()
+ -- İ: `tolower("İ")` is `i` which has length 1 while `İ` itself has
+ -- length 2 (in bytes).
+ -- Ⱥ: `tolower("Ⱥ")` is `ⱥ` which has length 2 while `Ⱥ` itself has
+ -- length 3 (in bytes).
+ --
+ -- Note: 'i' !=? 'İ' and 'ⱥ' !=? 'Ⱥ' on some systems.
+ -- Note: Built-in Nvim comparison (on systems lacking `strcasecmp`) works
+ -- only on ASCII characters.
+ it('vim.stricmp', function()
+ eq(0, funcs.luaeval('vim.stricmp("a", "A")'))
+ eq(0, funcs.luaeval('vim.stricmp("A", "a")'))
+ eq(0, funcs.luaeval('vim.stricmp("a", "a")'))
+ eq(0, funcs.luaeval('vim.stricmp("A", "A")'))
+
+ eq(0, funcs.luaeval('vim.stricmp("", "")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0", "\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0\\0", "\\0\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0", "\\0\\0\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0A", "\\0\\0\\0a")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0A")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0a")'))
+
+ eq(0, funcs.luaeval('vim.stricmp("a\\0", "A\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("A\\0", "a\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("a\\0", "a\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("A\\0", "A\\0")'))
+
+ eq(0, funcs.luaeval('vim.stricmp("\\0a", "\\0A")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0A", "\\0a")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0a", "\\0a")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0A", "\\0A")'))
+
+ eq(0, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0A\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0a\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0a\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0A\\0")'))
+
+ eq(-1, funcs.luaeval('vim.stricmp("a", "B")'))
+ eq(-1, funcs.luaeval('vim.stricmp("A", "b")'))
+ eq(-1, funcs.luaeval('vim.stricmp("a", "b")'))
+ eq(-1, funcs.luaeval('vim.stricmp("A", "B")'))
+
+ eq(-1, funcs.luaeval('vim.stricmp("", "\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0", "\\0\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0\\0", "\\0\\0\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0A", "\\0\\0\\0b")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0B")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0b")'))
+
+ eq(-1, funcs.luaeval('vim.stricmp("a\\0", "B\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("A\\0", "b\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("a\\0", "b\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("A\\0", "B\\0")'))
+
+ eq(-1, funcs.luaeval('vim.stricmp("\\0a", "\\0B")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0A", "\\0b")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0a", "\\0b")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0A", "\\0B")'))
+
+ eq(-1, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0B\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0b\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0b\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0B\\0")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("c", "B")'))
+ eq(1, funcs.luaeval('vim.stricmp("C", "b")'))
+ eq(1, funcs.luaeval('vim.stricmp("c", "b")'))
+ eq(1, funcs.luaeval('vim.stricmp("C", "B")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("\\0", "")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0", "\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0", "\\0\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0\\0", "\\0\\0\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0C", "\\0\\0\\0b")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0c", "\\0\\0\\0B")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0c", "\\0\\0\\0b")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("c\\0", "B\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("C\\0", "b\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("c\\0", "b\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("C\\0", "B\\0")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("c\\0", "B")'))
+ eq(1, funcs.luaeval('vim.stricmp("C\\0", "b")'))
+ eq(1, funcs.luaeval('vim.stricmp("c\\0", "b")'))
+ eq(1, funcs.luaeval('vim.stricmp("C\\0", "B")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("\\0c", "\\0B")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0C", "\\0b")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0c", "\\0b")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0C", "\\0B")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("\\0c\\0", "\\0B\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0b\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0c\\0", "\\0b\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0B\\0")'))
+ end)
+
+ it('vim.startswith', function()
+ eq(true, funcs.luaeval('vim.startswith("123", "1")'))
+ eq(true, funcs.luaeval('vim.startswith("123", "")'))
+ eq(true, funcs.luaeval('vim.startswith("123", "123")'))
+ eq(true, funcs.luaeval('vim.startswith("", "")'))
+
+ eq(false, funcs.luaeval('vim.startswith("123", " ")'))
+ eq(false, funcs.luaeval('vim.startswith("123", "2")'))
+ eq(false, funcs.luaeval('vim.startswith("123", "1234")'))
+
+ eq("string", type(pcall_err(funcs.luaeval, 'vim.startswith("123", nil)')))
+ eq("string", type(pcall_err(funcs.luaeval, 'vim.startswith(nil, "123")')))
+ end)
+
+ it('vim.endswith', function()
+ eq(true, funcs.luaeval('vim.endswith("123", "3")'))
+ eq(true, funcs.luaeval('vim.endswith("123", "")'))
+ eq(true, funcs.luaeval('vim.endswith("123", "123")'))
+ eq(true, funcs.luaeval('vim.endswith("", "")'))
+
+ eq(false, funcs.luaeval('vim.endswith("123", " ")'))
+ eq(false, funcs.luaeval('vim.endswith("123", "2")'))
+ eq(false, funcs.luaeval('vim.endswith("123", "1234")'))
+
+ eq("string", type(pcall_err(funcs.luaeval, 'vim.endswith("123", nil)')))
+ eq("string", type(pcall_err(funcs.luaeval, 'vim.endswith(nil, "123")')))
+ end)
+
+ it("vim.str_utfindex/str_byteindex", function()
+ exec_lua([[_G.test_text = "xy åäö ɧ 汉语 ↥ 🤦x🦄 å بِيَّ"]])
+ local indicies32 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,29,33,34,35,37,38,40,42,44,46,48}
+ local indicies16 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,28,29,33,33,34,35,37,38,40,42,44,46,48}
+ for i,k in pairs(indicies32) do
+ eq(k, exec_lua("return vim.str_byteindex(_G.test_text, ...)", i), i)
+ end
+ for i,k in pairs(indicies16) do
+ eq(k, exec_lua("return vim.str_byteindex(_G.test_text, ..., true)", i), i)
+ end
+ local i32, i16 = 0, 0
+ for k = 0,48 do
+ if indicies32[i32] < k then
+ i32 = i32 + 1
+ end
+ if indicies16[i16] < k then
+ i16 = i16 + 1
+ if indicies16[i16+1] == indicies16[i16] then
+ i16 = i16 + 1
+ end
+ end
+ eq({i32, i16}, exec_lua("return {vim.str_utfindex(_G.test_text, ...)}", k), k)
+ end
+ end)
+
+ it("vim.schedule", function()
+ exec_lua([[
+ test_table = {}
+ vim.schedule(function()
+ table.insert(test_table, "xx")
+ end)
+ table.insert(test_table, "yy")
+ ]])
+ eq({"yy","xx"}, exec_lua("return test_table"))
+
+ -- Validates args.
+ eq('Error executing lua: vim.schedule: expected function',
+ pcall_err(exec_lua, "vim.schedule('stringly')"))
+ eq('Error executing lua: vim.schedule: expected function',
+ pcall_err(exec_lua, "vim.schedule()"))
+
+ exec_lua([[
+ vim.schedule(function()
+ error("big failure\nvery async")
+ end)
+ ]])
+
+ feed("<cr>")
+ eq('Error executing vim.schedule lua callback: [string "<nvim>"]:2: big failure\nvery async', eval("v:errmsg"))
+
+ local screen = Screen.new(60,5)
+ screen:set_default_attr_ids({
+ [1] = {bold = true, foreground = Screen.colors.Blue1},
+ [2] = {bold = true, reverse = true},
+ [3] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ [4] = {bold = true, foreground = Screen.colors.SeaGreen4},
+ })
+ screen:attach()
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ -- nvim_command causes a vimL exception, check that it is properly caught
+ -- and propagated as an error message in async contexts.. #10809
+ exec_lua([[
+ vim.schedule(function()
+ vim.api.nvim_command(":echo 'err")
+ end)
+ ]])
+ screen:expect{grid=[[
+ |
+ {2: }|
+ {3:Error executing vim.schedule lua callback: [string "<nvim>"]}|
+ {3::2: Vim(echo):E115: Missing quote: 'err} |
+ {4:Press ENTER or type command to continue}^ |
+ ]]}
+ end)
+
+ it("vim.split", function()
+ local split = function(str, sep, plain)
+ return exec_lua('return vim.split(...)', str, sep, plain)
+ end
+
+ local tests = {
+ { "a,b", ",", false, { 'a', 'b' } },
+ { ":aa::bb:", ":", false, { '', 'aa', '', 'bb', '' } },
+ { "::ee::ff:", ":", false, { '', '', 'ee', '', 'ff', '' } },
+ { "ab", ".", false, { '', '', '' } },
+ { "a1b2c", "[0-9]", false, { 'a', 'b', 'c' } },
+ { "xy", "", false, { 'x', 'y' } },
+ { "here be dragons", " ", false, { "here", "be", "dragons"} },
+ { "axaby", "ab?", false, { '', 'x', 'y' } },
+ { "f v2v v3v w2w ", "([vw])2%1", false, { 'f ', ' v3v ', ' ' } },
+ { "", "", false, {} },
+ { "", "a", false, { '' } },
+ { "x*yz*oo*l", "*", true, { 'x', 'yz', 'oo', 'l' } },
+ }
+
+ for _, t in ipairs(tests) do
+ eq(t[4], split(t[1], t[2], t[3]))
+ end
+
+ local loops = {
+ { "abc", ".-" },
+ }
+
+ for _, t in ipairs(loops) do
+ matches(".*Infinite loop detected", pcall_err(split, t[1], t[2]))
+ end
+
+ -- Validates args.
+ eq(true, pcall(split, 'string', 'string'))
+ eq('Error executing lua: .../shared.lua: s: expected string, got number',
+ pcall_err(split, 1, 'string'))
+ eq('Error executing lua: .../shared.lua: sep: expected string, got number',
+ pcall_err(split, 'string', 1))
+ eq('Error executing lua: .../shared.lua: plain: expected boolean, got number',
+ pcall_err(split, 'string', 'string', 1))
+ end)
+
+ it('vim.trim', function()
+ local trim = function(s)
+ return exec_lua('return vim.trim(...)', s)
+ end
+
+ local trims = {
+ { " a", "a" },
+ { " b ", "b" },
+ { "\tc" , "c" },
+ { "r\n", "r" },
+ }
+
+ for _, t in ipairs(trims) do
+ assert(t[2], trim(t[1]))
+ end
+
+ -- Validates args.
+ eq('Error executing lua: .../shared.lua: s: expected string, got number',
+ pcall_err(trim, 2))
+ end)
+
+ it('vim.inspect', function()
+ -- just make sure it basically works, it has its own test suite
+ local inspect = function(t, opts)
+ return exec_lua('return vim.inspect(...)', t, opts)
+ end
+
+ eq('2', inspect(2))
+ eq('{+a = {+b = 1+}+}',
+ inspect({ a = { b = 1 } }, { newline = '+', indent = '' }))
+
+ -- special value vim.inspect.KEY works
+ eq('{ KEY_a = "x", KEY_b = "y"}', exec_lua([[
+ return vim.inspect({a="x", b="y"}, {newline = '', process = function(item, path)
+ if path[#path] == vim.inspect.KEY then
+ return 'KEY_'..item
+ end
+ return item
+ end})
+ ]]))
+ end)
+
+ it("vim.deepcopy", function()
+ ok(exec_lua([[
+ local a = { x = { 1, 2 }, y = 5}
+ local b = vim.deepcopy(a)
+
+ return b.x[1] == 1 and b.x[2] == 2 and b.y == 5 and vim.tbl_count(b) == 2
+ and tostring(a) ~= tostring(b)
+ ]]))
+
+ ok(exec_lua([[
+ local a = {}
+ local b = vim.deepcopy(a)
+
+ return vim.tbl_islist(b) and vim.tbl_count(b) == 0 and tostring(a) ~= tostring(b)
+ ]]))
+
+ ok(exec_lua([[
+ local a = vim.empty_dict()
+ local b = vim.deepcopy(a)
+
+ return not vim.tbl_islist(b) and vim.tbl_count(b) == 0
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = vim.empty_dict(), y = {}}
+ local b = vim.deepcopy(a)
+
+ return not vim.tbl_islist(b.x) and vim.tbl_islist(b.y)
+ and vim.tbl_count(b) == 2
+ and tostring(a) ~= tostring(b)
+ ]]))
+
+ ok(exec_lua([[
+ local f1 = function() return 1 end
+ local f2 = function() return 2 end
+ local t1 = {f = f1}
+ local t2 = vim.deepcopy(t1)
+ t1.f = f2
+ return t1.f() ~= t2.f()
+ ]]))
+
+ eq('Error executing lua: .../shared.lua: Cannot deepcopy object of type thread',
+ pcall_err(exec_lua, [[
+ local thread = coroutine.create(function () return 0 end)
+ local t = {thr = thread}
+ vim.deepcopy(t)
+ ]]))
+ end)
+
+ it('vim.pesc', function()
+ eq('foo%-bar', exec_lua([[return vim.pesc('foo-bar')]]))
+ eq('foo%%%-bar', exec_lua([[return vim.pesc(vim.pesc('foo-bar'))]]))
+
+ -- Validates args.
+ eq('Error executing lua: .../shared.lua: s: expected string, got number',
+ pcall_err(exec_lua, [[return vim.pesc(2)]]))
+ end)
+
+ it('vim.tbl_keys', function()
+ eq({}, exec_lua("return vim.tbl_keys({})"))
+ for _, v in pairs(exec_lua("return vim.tbl_keys({'a', 'b', 'c'})")) do
+ eq(true, exec_lua("return vim.tbl_contains({ 1, 2, 3 }, ...)", v))
+ end
+ for _, v in pairs(exec_lua("return vim.tbl_keys({a=1, b=2, c=3})")) do
+ eq(true, exec_lua("return vim.tbl_contains({ 'a', 'b', 'c' }, ...)", v))
+ end
+ end)
+
+ it('vim.tbl_values', function()
+ eq({}, exec_lua("return vim.tbl_values({})"))
+ for _, v in pairs(exec_lua("return vim.tbl_values({'a', 'b', 'c'})")) do
+ eq(true, exec_lua("return vim.tbl_contains({ 'a', 'b', 'c' }, ...)", v))
+ end
+ for _, v in pairs(exec_lua("return vim.tbl_values({a=1, b=2, c=3})")) do
+ eq(true, exec_lua("return vim.tbl_contains({ 1, 2, 3 }, ...)", v))
+ end
+ end)
+
+ it('vim.tbl_map', function()
+ eq({}, exec_lua([[
+ return vim.tbl_map(function(v) return v * 2 end, {})
+ ]]))
+ eq({2, 4, 6}, exec_lua([[
+ return vim.tbl_map(function(v) return v * 2 end, {1, 2, 3})
+ ]]))
+ eq({{i=2}, {i=4}, {i=6}}, exec_lua([[
+ return vim.tbl_map(function(v) return { i = v.i * 2 } end, {{i=1}, {i=2}, {i=3}})
+ ]]))
+ end)
+
+ it('vim.tbl_filter', function()
+ eq({}, exec_lua([[
+ return vim.tbl_filter(function(v) return (v % 2) == 0 end, {})
+ ]]))
+ eq({2}, exec_lua([[
+ return vim.tbl_filter(function(v) return (v % 2) == 0 end, {1, 2, 3})
+ ]]))
+ eq({{i=2}}, exec_lua([[
+ return vim.tbl_filter(function(v) return (v.i % 2) == 0 end, {{i=1}, {i=2}, {i=3}})
+ ]]))
+ end)
+
+ it('vim.tbl_islist', function()
+ eq(true, exec_lua("return vim.tbl_islist({})"))
+ eq(false, exec_lua("return vim.tbl_islist(vim.empty_dict())"))
+ eq(true, exec_lua("return vim.tbl_islist({'a', 'b', 'c'})"))
+ eq(false, exec_lua("return vim.tbl_islist({'a', '32', a='hello', b='baz'})"))
+ eq(false, exec_lua("return vim.tbl_islist({1, a='hello', b='baz'})"))
+ eq(false, exec_lua("return vim.tbl_islist({a='hello', b='baz', 1})"))
+ eq(false, exec_lua("return vim.tbl_islist({1, 2, nil, a='hello'})"))
+ end)
+
+ it('vim.tbl_isempty', function()
+ eq(true, exec_lua("return vim.tbl_isempty({})"))
+ eq(false, exec_lua("return vim.tbl_isempty({ 1, 2, 3 })"))
+ eq(false, exec_lua("return vim.tbl_isempty({a=1, b=2, c=3})"))
+ end)
+
+ it('vim.tbl_extend', function()
+ ok(exec_lua([[
+ local a = {x = 1}
+ local b = {y = 2}
+ local c = vim.tbl_extend("keep", a, b)
+
+ return c.x == 1 and b.y == 2 and vim.tbl_count(c) == 2
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = 1}
+ local b = {y = 2}
+ local c = {z = 3}
+ local d = vim.tbl_extend("keep", a, b, c)
+
+ return d.x == 1 and d.y == 2 and d.z == 3 and vim.tbl_count(d) == 3
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = 1}
+ local b = {x = 3}
+ local c = vim.tbl_extend("keep", a, b)
+
+ return c.x == 1 and vim.tbl_count(c) == 1
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = 1}
+ local b = {x = 3}
+ local c = vim.tbl_extend("force", a, b)
+
+ return c.x == 3 and vim.tbl_count(c) == 1
+ ]]))
+
+ ok(exec_lua([[
+ local a = vim.empty_dict()
+ local b = {}
+ local c = vim.tbl_extend("keep", a, b)
+
+ return not vim.tbl_islist(c) and vim.tbl_count(c) == 0
+ ]]))
+
+ ok(exec_lua([[
+ local a = {}
+ local b = vim.empty_dict()
+ local c = vim.tbl_extend("keep", a, b)
+
+ return vim.tbl_islist(c) and vim.tbl_count(c) == 0
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = {a = 1, b = 2}}
+ local b = {x = {a = 2, c = {y = 3}}}
+ local c = vim.tbl_extend("keep", a, b)
+
+ local count = 0
+ for _ in pairs(c) do count = count + 1 end
+
+ return c.x.a == 1 and c.x.b == 2 and c.x.c == nil and count == 1
+ ]]))
+
+ eq('Error executing lua: .../shared.lua: invalid "behavior": nil',
+ pcall_err(exec_lua, [[
+ return vim.tbl_extend()
+ ]])
+ )
+
+ eq('Error executing lua: .../shared.lua: wrong number of arguments (given 1, expected at least 3)',
+ pcall_err(exec_lua, [[
+ return vim.tbl_extend("keep")
+ ]])
+ )
+
+ eq('Error executing lua: .../shared.lua: wrong number of arguments (given 2, expected at least 3)',
+ pcall_err(exec_lua, [[
+ return vim.tbl_extend("keep", {})
+ ]])
+ )
+ end)
+
+ it('vim.tbl_deep_extend', function()
+ ok(exec_lua([[
+ local a = {x = {a = 1, b = 2}}
+ local b = {x = {a = 2, c = {y = 3}}}
+ local c = vim.tbl_deep_extend("keep", a, b)
+
+ local count = 0
+ for _ in pairs(c) do count = count + 1 end
+
+ return c.x.a == 1 and c.x.b == 2 and c.x.c.y == 3 and count == 1
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = {a = 1, b = 2}}
+ local b = {x = {a = 2, c = {y = 3}}}
+ local c = vim.tbl_deep_extend("force", a, b)
+
+ local count = 0
+ for _ in pairs(c) do count = count + 1 end
+
+ return c.x.a == 2 and c.x.b == 2 and c.x.c.y == 3 and count == 1
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = {a = 1, b = 2}}
+ local b = {x = {a = 2, c = {y = 3}}}
+ local c = {x = {c = 4, d = {y = 4}}}
+ local d = vim.tbl_deep_extend("keep", a, b, c)
+
+ local count = 0
+ for _ in pairs(c) do count = count + 1 end
+
+ return d.x.a == 1 and d.x.b == 2 and d.x.c.y == 3 and d.x.d.y == 4 and count == 1
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = {a = 1, b = 2}}
+ local b = {x = {a = 2, c = {y = 3}}}
+ local c = {x = {c = 4, d = {y = 4}}}
+ local d = vim.tbl_deep_extend("force", a, b, c)
+
+ local count = 0
+ for _ in pairs(c) do count = count + 1 end
+
+ return d.x.a == 2 and d.x.b == 2 and d.x.c == 4 and d.x.d.y == 4 and count == 1
+ ]]))
+
+ ok(exec_lua([[
+ local a = vim.empty_dict()
+ local b = {}
+ local c = vim.tbl_deep_extend("keep", a, b)
+
+ local count = 0
+ for _ in pairs(c) do count = count + 1 end
+
+ return not vim.tbl_islist(c) and count == 0
+ ]]))
+
+ ok(exec_lua([[
+ local a = {}
+ local b = vim.empty_dict()
+ local c = vim.tbl_deep_extend("keep", a, b)
+
+ local count = 0
+ for _ in pairs(c) do count = count + 1 end
+
+ return vim.tbl_islist(c) and count == 0
+ ]]))
+
+ eq('Error executing lua: .../shared.lua: invalid "behavior": nil',
+ pcall_err(exec_lua, [[
+ return vim.tbl_deep_extend()
+ ]])
+ )
+
+ eq('Error executing lua: .../shared.lua: wrong number of arguments (given 1, expected at least 3)',
+ pcall_err(exec_lua, [[
+ return vim.tbl_deep_extend("keep")
+ ]])
+ )
+
+ eq('Error executing lua: .../shared.lua: wrong number of arguments (given 2, expected at least 3)',
+ pcall_err(exec_lua, [[
+ return vim.tbl_deep_extend("keep", {})
+ ]])
+ )
+ end)
+
+ it('vim.tbl_count', function()
+ eq(0, exec_lua [[ return vim.tbl_count({}) ]])
+ eq(0, exec_lua [[ return vim.tbl_count(vim.empty_dict()) ]])
+ eq(0, exec_lua [[ return vim.tbl_count({nil}) ]])
+ eq(0, exec_lua [[ return vim.tbl_count({a=nil}) ]])
+ eq(1, exec_lua [[ return vim.tbl_count({1}) ]])
+ eq(2, exec_lua [[ return vim.tbl_count({1, 2}) ]])
+ eq(2, exec_lua [[ return vim.tbl_count({1, nil, 3}) ]])
+ eq(1, exec_lua [[ return vim.tbl_count({a=1}) ]])
+ eq(2, exec_lua [[ return vim.tbl_count({a=1, b=2}) ]])
+ eq(2, exec_lua [[ return vim.tbl_count({a=1, b=nil, c=3}) ]])
+ end)
+
+ it('vim.deep_equal', function()
+ eq(true, exec_lua [[ return vim.deep_equal({a=1}, {a=1}) ]])
+ eq(true, exec_lua [[ return vim.deep_equal({a={b=1}}, {a={b=1}}) ]])
+ eq(true, exec_lua [[ return vim.deep_equal({a={b={nil}}}, {a={b={}}}) ]])
+ eq(true, exec_lua [[ return vim.deep_equal({a=1, [5]=5}, {nil,nil,nil,nil,5,a=1}) ]])
+ eq(false, exec_lua [[ return vim.deep_equal(1, {nil,nil,nil,nil,5,a=1}) ]])
+ eq(false, exec_lua [[ return vim.deep_equal(1, 3) ]])
+ eq(false, exec_lua [[ return vim.deep_equal(nil, 3) ]])
+ eq(false, exec_lua [[ return vim.deep_equal({a=1}, {a=2}) ]])
+ end)
+
+ it('vim.list_extend', function()
+ eq({1,2,3}, exec_lua [[ return vim.list_extend({1}, {2,3}) ]])
+ eq('Error executing lua: .../shared.lua: src: expected table, got nil',
+ pcall_err(exec_lua, [[ return vim.list_extend({1}, nil) ]]))
+ eq({1,2}, exec_lua [[ return vim.list_extend({1}, {2;a=1}) ]])
+ eq(true, exec_lua [[ local a = {1} return vim.list_extend(a, {2;a=1}) == a ]])
+ eq({2}, exec_lua [[ return vim.list_extend({}, {2;a=1}, 1) ]])
+ eq({}, exec_lua [[ return vim.list_extend({}, {2;a=1}, 2) ]])
+ eq({}, exec_lua [[ return vim.list_extend({}, {2;a=1}, 1, -1) ]])
+ eq({2}, exec_lua [[ return vim.list_extend({}, {2;a=1}, -1, 2) ]])
+ end)
+
+ it('vim.tbl_add_reverse_lookup', function()
+ eq(true, exec_lua [[
+ local a = { A = 1 }
+ vim.tbl_add_reverse_lookup(a)
+ return vim.deep_equal(a, { A = 1; [1] = 'A'; })
+ ]])
+ -- Throw an error for trying to do it twice (run into an existing key)
+ local code = [[
+ local res = {}
+ local a = { A = 1 }
+ vim.tbl_add_reverse_lookup(a)
+ assert(vim.deep_equal(a, { A = 1; [1] = 'A'; }))
+ vim.tbl_add_reverse_lookup(a)
+ ]]
+ matches('Error executing lua: .../shared.lua: The reverse lookup found an existing value for "[1A]" while processing key "[1A]"',
+ pcall_err(exec_lua, code))
+ end)
+
+ it('vim.call, vim.fn', function()
+ eq(true, exec_lua([[return vim.call('sin', 0.0) == 0.0 ]]))
+ eq(true, exec_lua([[return vim.fn.sin(0.0) == 0.0 ]]))
+ -- compat: nvim_call_function uses "special" value for vimL float
+ eq(false, exec_lua([[return vim.api.nvim_call_function('sin', {0.0}) == 0.0 ]]))
+
+ source([[
+ func! FooFunc(test)
+ let g:test = a:test
+ return {}
+ endfunc
+ func! VarArg(...)
+ return a:000
+ endfunc
+ func! Nilly()
+ return [v:null, v:null]
+ endfunc
+ ]])
+ eq(true, exec_lua([[return next(vim.fn.FooFunc(3)) == nil ]]))
+ eq(3, eval("g:test"))
+ -- compat: nvim_call_function uses "special" value for empty dict
+ eq(true, exec_lua([[return next(vim.api.nvim_call_function("FooFunc", {5})) == true ]]))
+ eq(5, eval("g:test"))
+
+ eq({2, "foo", true}, exec_lua([[return vim.fn.VarArg(2, "foo", true)]]))
+
+ eq(true, exec_lua([[
+ local x = vim.fn.Nilly()
+ return #x == 2 and x[1] == vim.NIL and x[2] == vim.NIL
+ ]]))
+ eq({NIL, NIL}, exec_lua([[return vim.fn.Nilly()]]))
+
+ -- error handling
+ eq({false, 'Vim:E714: List required'}, exec_lua([[return {pcall(vim.fn.add, "aa", "bb")}]]))
+ end)
+
+ it('vim.rpcrequest and vim.rpcnotify', function()
+ exec_lua([[
+ chan = vim.fn.jobstart({'cat'}, {rpc=true})
+ vim.rpcrequest(chan, 'nvim_set_current_line', 'meow')
+ ]])
+ eq('meow', meths.get_current_line())
+ command("let x = [3, 'aa', v:true, v:null]")
+ eq(true, exec_lua([[
+ ret = vim.rpcrequest(chan, 'nvim_get_var', 'x')
+ return #ret == 4 and ret[1] == 3 and ret[2] == 'aa' and ret[3] == true and ret[4] == vim.NIL
+ ]]))
+ eq({3, 'aa', true, NIL}, exec_lua([[return ret]]))
+
+ eq({{}, {}, false, true}, exec_lua([[
+ vim.rpcrequest(chan, 'nvim_exec', 'let xx = {}\nlet yy = []', false)
+ local dict = vim.rpcrequest(chan, 'nvim_eval', 'xx')
+ local list = vim.rpcrequest(chan, 'nvim_eval', 'yy')
+ return {dict, list, vim.tbl_islist(dict), vim.tbl_islist(list)}
+ ]]))
+
+ exec_lua([[
+ vim.rpcrequest(chan, 'nvim_set_var', 'aa', {})
+ vim.rpcrequest(chan, 'nvim_set_var', 'bb', vim.empty_dict())
+ ]])
+ eq({1, 1}, eval('[type(g:aa) == type([]), type(g:bb) == type({})]'))
+
+ -- error handling
+ eq({false, 'Invalid channel: 23'},
+ exec_lua([[return {pcall(vim.rpcrequest, 23, 'foo')}]]))
+ eq({false, 'Invalid channel: 23'},
+ exec_lua([[return {pcall(vim.rpcnotify, 23, 'foo')}]]))
+
+ eq({false, 'Vim:E121: Undefined variable: foobar'},
+ exec_lua([[return {pcall(vim.rpcrequest, chan, 'nvim_eval', "foobar")}]]))
+
+
+ -- rpcnotify doesn't wait on request
+ eq('meow', exec_lua([[
+ vim.rpcnotify(chan, 'nvim_set_current_line', 'foo')
+ return vim.api.nvim_get_current_line()
+ ]]))
+ retry(10, nil, function()
+ eq('foo', meths.get_current_line())
+ end)
+
+ local screen = Screen.new(50,7)
+ screen:set_default_attr_ids({
+ [1] = {bold = true, foreground = Screen.colors.Blue1},
+ [2] = {bold = true, reverse = true},
+ [3] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ [4] = {bold = true, foreground = Screen.colors.SeaGreen4},
+ })
+ screen:attach()
+ exec_lua([[
+ timer = vim.loop.new_timer()
+ timer:start(20, 0, function ()
+ -- notify ok (executed later when safe)
+ vim.rpcnotify(chan, 'nvim_set_var', 'yy', {3, vim.NIL})
+ -- rpcrequest an error
+ vim.rpcrequest(chan, 'nvim_set_current_line', 'bork')
+ end)
+ ]])
+ screen:expect{grid=[[
+ foo |
+ {1:~ }|
+ {2: }|
+ {3:Error executing luv callback:} |
+ {3:[string "<nvim>"]:6: E5560: rpcrequest must not be}|
+ {3: called in a lua loop callback} |
+ {4:Press ENTER or type command to continue}^ |
+ ]]}
+ feed('<cr>')
+ eq({3, NIL}, meths.get_var('yy'))
+
+ exec_lua([[timer:close()]])
+ end)
+
+ it('vim.empty_dict()', function()
+ eq({true, false, true, true}, exec_lua([[
+ vim.api.nvim_set_var('listy', {})
+ vim.api.nvim_set_var('dicty', vim.empty_dict())
+ local listy = vim.fn.eval("listy")
+ local dicty = vim.fn.eval("dicty")
+ return {vim.tbl_islist(listy), vim.tbl_islist(dicty), next(listy) == nil, next(dicty) == nil}
+ ]]))
+
+ -- vim.empty_dict() gives new value each time
+ -- equality is not overriden (still by ref)
+ -- non-empty table uses the usual heuristics (ignores the tag)
+ eq({false, {"foo"}, {namey="bar"}}, exec_lua([[
+ local aa = vim.empty_dict()
+ local bb = vim.empty_dict()
+ local equally = (aa == bb)
+ aa[1] = "foo"
+ bb["namey"] = "bar"
+ return {equally, aa, bb}
+ ]]))
+
+ eq("{ {}, vim.empty_dict() }", exec_lua("return vim.inspect({{}, vim.empty_dict()})"))
+ eq('{}', exec_lua([[ return vim.fn.json_encode(vim.empty_dict()) ]]))
+ eq('{"a": {}, "b": []}', exec_lua([[ return vim.fn.json_encode({a=vim.empty_dict(), b={}}) ]]))
+ end)
+
+ it('vim.validate', function()
+ exec_lua("vim.validate{arg1={{}, 'table' }}")
+ exec_lua("vim.validate{arg1={{}, 't' }}")
+ exec_lua("vim.validate{arg1={nil, 't', true }}")
+ exec_lua("vim.validate{arg1={{ foo='foo' }, 't' }}")
+ exec_lua("vim.validate{arg1={{ 'foo' }, 't' }}")
+ exec_lua("vim.validate{arg1={'foo', 'string' }}")
+ exec_lua("vim.validate{arg1={'foo', 's' }}")
+ exec_lua("vim.validate{arg1={'', 's' }}")
+ exec_lua("vim.validate{arg1={nil, 's', true }}")
+ exec_lua("vim.validate{arg1={1, 'number' }}")
+ exec_lua("vim.validate{arg1={1, 'n' }}")
+ exec_lua("vim.validate{arg1={0, 'n' }}")
+ exec_lua("vim.validate{arg1={0.1, 'n' }}")
+ exec_lua("vim.validate{arg1={nil, 'n', true }}")
+ exec_lua("vim.validate{arg1={true, 'boolean' }}")
+ exec_lua("vim.validate{arg1={true, 'b' }}")
+ exec_lua("vim.validate{arg1={false, 'b' }}")
+ exec_lua("vim.validate{arg1={nil, 'b', true }}")
+ exec_lua("vim.validate{arg1={function()end, 'function' }}")
+ exec_lua("vim.validate{arg1={function()end, 'f' }}")
+ exec_lua("vim.validate{arg1={nil, 'f', true }}")
+ exec_lua("vim.validate{arg1={nil, 'nil' }}")
+ exec_lua("vim.validate{arg1={nil, 'nil', true }}")
+ exec_lua("vim.validate{arg1={coroutine.create(function()end), 'thread' }}")
+ exec_lua("vim.validate{arg1={nil, 'thread', true }}")
+ exec_lua("vim.validate{arg1={{}, 't' }, arg2={ 'foo', 's' }}")
+ exec_lua("vim.validate{arg1={2, function(a) return (a % 2) == 0 end, 'even number' }}")
+
+ eq("Error executing lua: .../shared.lua: 1: expected table, got number",
+ pcall_err(exec_lua, "vim.validate{ 1, 'x' }"))
+ eq("Error executing lua: .../shared.lua: invalid type name: x",
+ pcall_err(exec_lua, "vim.validate{ arg1={ 1, 'x' }}"))
+ eq("Error executing lua: .../shared.lua: invalid type name: 1",
+ pcall_err(exec_lua, "vim.validate{ arg1={ 1, 1 }}"))
+ eq("Error executing lua: .../shared.lua: invalid type name: nil",
+ pcall_err(exec_lua, "vim.validate{ arg1={ 1 }}"))
+
+ -- Validated parameters are required by default.
+ eq("Error executing lua: .../shared.lua: arg1: expected string, got nil",
+ pcall_err(exec_lua, "vim.validate{ arg1={ nil, 's' }}"))
+ -- Explicitly required.
+ eq("Error executing lua: .../shared.lua: arg1: expected string, got nil",
+ pcall_err(exec_lua, "vim.validate{ arg1={ nil, 's', false }}"))
+
+ eq("Error executing lua: .../shared.lua: arg1: expected table, got number",
+ pcall_err(exec_lua, "vim.validate{arg1={1, 't'}}"))
+ eq("Error executing lua: .../shared.lua: arg2: expected string, got number",
+ pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={1, 's'}}"))
+ eq("Error executing lua: .../shared.lua: arg2: expected string, got nil",
+ pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={nil, 's'}}"))
+ eq("Error executing lua: .../shared.lua: arg2: expected string, got nil",
+ pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={nil, 's'}}"))
+ eq("Error executing lua: .../shared.lua: arg1: expected even number, got 3",
+ pcall_err(exec_lua, "vim.validate{arg1={3, function(a) return a == 1 end, 'even number'}}"))
+ eq("Error executing lua: .../shared.lua: arg1: expected ?, got 3",
+ pcall_err(exec_lua, "vim.validate{arg1={3, function(a) return a == 1 end}}"))
+ end)
+
+ it('vim.is_callable', function()
+ eq(true, exec_lua("return vim.is_callable(function()end)"))
+ eq(true, exec_lua([[
+ local meta = { __call = function()end }
+ local function new_callable()
+ return setmetatable({}, meta)
+ end
+ local callable = new_callable()
+ return vim.is_callable(callable)
+ ]]))
+
+ eq(false, exec_lua("return vim.is_callable(1)"))
+ eq(false, exec_lua("return vim.is_callable('foo')"))
+ eq(false, exec_lua("return vim.is_callable({})"))
+ end)
+
+ it('vim.g', function()
+ exec_lua [[
+ vim.api.nvim_set_var("testing", "hi")
+ vim.api.nvim_set_var("other", 123)
+ vim.api.nvim_set_var("to_delete", {hello="world"})
+ ]]
+
+ eq('hi', funcs.luaeval "vim.g.testing")
+ eq(123, funcs.luaeval "vim.g.other")
+ eq(NIL, funcs.luaeval "vim.g.nonexistant")
+
+ eq({hello="world"}, funcs.luaeval "vim.g.to_delete")
+ exec_lua [[
+ vim.g.to_delete = nil
+ ]]
+ eq(NIL, funcs.luaeval "vim.g.to_delete")
+ end)
+
+ it('vim.b', function()
+ exec_lua [[
+ vim.api.nvim_buf_set_var(0, "testing", "hi")
+ vim.api.nvim_buf_set_var(0, "other", 123)
+ vim.api.nvim_buf_set_var(0, "to_delete", {hello="world"})
+ ]]
+
+ eq('hi', funcs.luaeval "vim.b.testing")
+ eq(123, funcs.luaeval "vim.b.other")
+ eq(NIL, funcs.luaeval "vim.b.nonexistant")
+
+ eq({hello="world"}, funcs.luaeval "vim.b.to_delete")
+ exec_lua [[
+ vim.b.to_delete = nil
+ ]]
+ eq(NIL, funcs.luaeval "vim.b.to_delete")
+
+ exec_lua [[
+ vim.cmd "vnew"
+ ]]
+
+ eq(NIL, funcs.luaeval "vim.b.testing")
+ eq(NIL, funcs.luaeval "vim.b.other")
+ eq(NIL, funcs.luaeval "vim.b.nonexistant")
+ end)
+
+ it('vim.w', function()
+ exec_lua [[
+ vim.api.nvim_win_set_var(0, "testing", "hi")
+ vim.api.nvim_win_set_var(0, "other", 123)
+ vim.api.nvim_win_set_var(0, "to_delete", {hello="world"})
+ ]]
+
+ eq('hi', funcs.luaeval "vim.w.testing")
+ eq(123, funcs.luaeval "vim.w.other")
+ eq(NIL, funcs.luaeval "vim.w.nonexistant")
+
+ eq({hello="world"}, funcs.luaeval "vim.w.to_delete")
+ exec_lua [[
+ vim.w.to_delete = nil
+ ]]
+ eq(NIL, funcs.luaeval "vim.w.to_delete")
+
+ exec_lua [[
+ vim.cmd "vnew"
+ ]]
+
+ eq(NIL, funcs.luaeval "vim.w.testing")
+ eq(NIL, funcs.luaeval "vim.w.other")
+ eq(NIL, funcs.luaeval "vim.w.nonexistant")
+ end)
+
+ it('vim.t', function()
+ exec_lua [[
+ vim.api.nvim_tabpage_set_var(0, "testing", "hi")
+ vim.api.nvim_tabpage_set_var(0, "other", 123)
+ vim.api.nvim_tabpage_set_var(0, "to_delete", {hello="world"})
+ ]]
+
+ eq('hi', funcs.luaeval "vim.t.testing")
+ eq(123, funcs.luaeval "vim.t.other")
+ eq(NIL, funcs.luaeval "vim.t.nonexistant")
+
+ eq({hello="world"}, funcs.luaeval "vim.t.to_delete")
+ exec_lua [[
+ vim.t.to_delete = nil
+ ]]
+ eq(NIL, funcs.luaeval "vim.t.to_delete")
+
+ exec_lua [[
+ vim.cmd "tabnew"
+ ]]
+
+ eq(NIL, funcs.luaeval "vim.t.testing")
+ eq(NIL, funcs.luaeval "vim.t.other")
+ eq(NIL, funcs.luaeval "vim.t.nonexistant")
+ end)
+
+ it('vim.env', function()
+ exec_lua [[
+ vim.fn.setenv("A", 123)
+ ]]
+ eq('123', funcs.luaeval "vim.env.A")
+ eq(true, funcs.luaeval "vim.env.B == nil")
+ end)
+
+ it('vim.v', function()
+ eq(funcs.luaeval "vim.api.nvim_get_vvar('progpath')", funcs.luaeval "vim.v.progpath")
+ eq(false, funcs.luaeval "vim.v['false']")
+ eq(NIL, funcs.luaeval "vim.v.null")
+ end)
+
+ it('vim.bo', function()
+ eq('', funcs.luaeval "vim.bo.filetype")
+ exec_lua [[
+ vim.api.nvim_buf_set_option(0, "filetype", "markdown")
+ BUF = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_buf_set_option(BUF, "modifiable", false)
+ ]]
+ eq(false, funcs.luaeval "vim.bo.modified")
+ eq('markdown', funcs.luaeval "vim.bo.filetype")
+ eq(false, funcs.luaeval "vim.bo[BUF].modifiable")
+ exec_lua [[
+ vim.bo.filetype = ''
+ vim.bo[BUF].modifiable = true
+ ]]
+ eq('', funcs.luaeval "vim.bo.filetype")
+ eq(true, funcs.luaeval "vim.bo[BUF].modifiable")
+ matches("^Error executing lua: .*: Invalid option name: 'nosuchopt'$",
+ pcall_err(exec_lua, 'return vim.bo.nosuchopt'))
+ matches("^Error executing lua: .*: Expected lua string$",
+ pcall_err(exec_lua, 'return vim.bo[0][0].autoread'))
+ end)
+
+ it('vim.wo', function()
+ exec_lua [[
+ vim.api.nvim_win_set_option(0, "cole", 2)
+ vim.cmd "split"
+ vim.api.nvim_win_set_option(0, "cole", 2)
+ ]]
+ eq(2, funcs.luaeval "vim.wo.cole")
+ exec_lua [[
+ vim.wo.conceallevel = 0
+ ]]
+ eq(0, funcs.luaeval "vim.wo.cole")
+ eq(0, funcs.luaeval "vim.wo[0].cole")
+ eq(0, funcs.luaeval "vim.wo[1001].cole")
+ matches("^Error executing lua: .*: Invalid option name: 'notanopt'$",
+ pcall_err(exec_lua, 'return vim.wo.notanopt'))
+ matches("^Error executing lua: .*: Expected lua string$",
+ pcall_err(exec_lua, 'return vim.wo[0][0].list'))
+ eq(2, funcs.luaeval "vim.wo[1000].cole")
+ exec_lua [[
+ vim.wo[1000].cole = 0
+ ]]
+ eq(0, funcs.luaeval "vim.wo[1000].cole")
+ end)
+
+ it('vim.cmd', function()
+ exec_lua [[
+ vim.cmd "autocmd BufNew * ++once lua BUF = vim.fn.expand('<abuf>')"
+ vim.cmd "new"
+ ]]
+ eq('2', funcs.luaeval "BUF")
+ eq(2, funcs.luaeval "#vim.api.nvim_list_bufs()")
+ end)
+
+ it('vim.regex', function()
+ exec_lua [[
+ re1 = vim.regex"ab\\+c"
+ vim.cmd "set nomagic ignorecase"
+ re2 = vim.regex"xYz"
+ ]]
+ eq({}, exec_lua[[return {re1:match_str("x ac")}]])
+ eq({3,7}, exec_lua[[return {re1:match_str("ac abbc")}]])
+
+ meths.buf_set_lines(0, 0, -1, true, {"yy", "abc abbc"})
+ eq({}, exec_lua[[return {re1:match_line(0, 0)}]])
+ eq({0,3}, exec_lua[[return {re1:match_line(0, 1)}]])
+ eq({3,7}, exec_lua[[return {re1:match_line(0, 1, 1)}]])
+ eq({3,7}, exec_lua[[return {re1:match_line(0, 1, 1, 8)}]])
+ eq({}, exec_lua[[return {re1:match_line(0, 1, 1, 7)}]])
+ eq({0,3}, exec_lua[[return {re1:match_line(0, 1, 0, 7)}]])
+ end)
+
+ it('vim.defer_fn', function()
+ eq(false, exec_lua [[
+ vim.g.test = false
+ vim.defer_fn(function() vim.g.test = true end, 150)
+ return vim.g.test
+ ]])
+ exec_lua [[vim.wait(1000, function() return vim.g.test end)]]
+ eq(true, exec_lua[[return vim.g.test]])
+ end)
+
+ it('vim.region', function()
+ helpers.insert(helpers.dedent( [[
+ text tααt tααt text
+ text tαxt txtα tex
+ text tαxt tαxt
+ ]]))
+ eq({5,15}, exec_lua[[ return vim.region(0,{1,5},{1,14},'v',true)[1] ]])
+ end)
+
+ describe('vim.wait', function()
+ before_each(function()
+ exec_lua[[
+ -- high precision timer
+ get_time = function()
+ return vim.fn.reltimefloat(vim.fn.reltime())
+ end
+ ]]
+ end)
+
+ it('should run from lua', function()
+ exec_lua[[vim.wait(100, function() return true end)]]
+ end)
+
+ it('should wait the expected time if false', function()
+ eq({time = true, wait_result = {false, -1}}, exec_lua[[
+ start_time = get_time()
+ wait_succeed, wait_fail_val = vim.wait(200, function() return false end)
+
+ return {
+ -- 150ms waiting or more results in true. Flaky tests are bad.
+ time = (start_time + 0.15) < get_time(),
+ wait_result = {wait_succeed, wait_fail_val}
+ }
+ ]])
+ end)
+
+
+ it('should not block other events', function()
+ eq({time = true, wait_result = true}, exec_lua[[
+ start_time = get_time()
+
+ vim.g.timer_result = false
+ timer = vim.loop.new_timer()
+ timer:start(100, 0, vim.schedule_wrap(function()
+ vim.g.timer_result = true
+ end))
+
+ -- Would wait ten seconds if results blocked.
+ wait_result = vim.wait(10000, function() return vim.g.timer_result end)
+
+ return {
+ time = (start_time + 5) > get_time(),
+ wait_result = wait_result,
+ }
+ ]])
+ end)
+
+ it('should work with vim.defer_fn', function()
+ eq({time = true, wait_result = true}, exec_lua[[
+ start_time = get_time()
+
+ vim.defer_fn(function() vim.g.timer_result = true end, 100)
+ wait_result = vim.wait(10000, function() return vim.g.timer_result end)
+
+ return {
+ time = (start_time + 5) > get_time(),
+ wait_result = wait_result,
+ }
+ ]])
+ end)
+
+ it('should require functions to be passed', function()
+ local pcall_result = exec_lua [[
+ return {pcall(function() vim.wait(1000, 13) end)}
+ ]]
+
+ eq(pcall_result[1], false)
+ matches('condition must be a function', pcall_result[2])
+ end)
+
+ it('should not crash when callback errors', function()
+ local pcall_result = exec_lua [[
+ return {pcall(function() vim.wait(1000, function() error("As Expected") end) end)}
+ ]]
+
+ eq(pcall_result[1], false)
+ matches('As Expected', pcall_result[2])
+ end)
+
+ it('should call callbacks exactly once if they return true immediately', function()
+ eq(true, exec_lua [[
+ vim.g.wait_count = 0
+ vim.wait(1000, function()
+ vim.g.wait_count = vim.g.wait_count + 1
+ return true
+ end, 20)
+ return vim.g.wait_count == 1
+ ]])
+ end)
+
+ it('should call callbacks few times with large `interval`', function()
+ eq(true, exec_lua [[
+ vim.g.wait_count = 0
+ vim.wait(50, function() vim.g.wait_count = vim.g.wait_count + 1 end, 200)
+ return vim.g.wait_count < 5
+ ]])
+ end)
+
+ it('should play nice with `not` when fails', function()
+ eq(true, exec_lua [[
+ if not vim.wait(50, function() end) then
+ return true
+ end
+
+ return false
+ ]])
+ end)
+
+ it('should play nice with `if` when success', function()
+ eq(true, exec_lua [[
+ if vim.wait(50, function() return true end) then
+ return true
+ end
+
+ return false
+ ]])
+ end)
+
+ it('should return immediately with false if timeout is 0', function()
+ eq({false, -1}, exec_lua [[
+ return {
+ vim.wait(0, function() return false end)
+ }
+ ]])
+ end)
+
+ it('should work with tables with __call', function()
+ eq(true, exec_lua [[
+ local t = setmetatable({}, {__call = function(...) return true end})
+ return vim.wait(100, t, 10)
+ ]])
+ end)
+
+ it('should work with tables with __call that change', function()
+ eq(true, exec_lua [[
+ local t = {count = 0}
+ setmetatable(t, {
+ __call = function()
+ t.count = t.count + 1
+ return t.count > 3
+ end
+ })
+
+ return vim.wait(1000, t, 10)
+ ]])
+ end)
+
+ it('should not work with negative intervals', function()
+ local pcall_result = exec_lua [[
+ return pcall(function() vim.wait(1000, function() return false end, -1) end)
+ ]]
+
+ eq(false, pcall_result)
+ end)
+
+ it('should not work with weird intervals', function()
+ local pcall_result = exec_lua [[
+ return pcall(function() vim.wait(1000, function() return false end, 'a string value') end)
+ ]]
+
+ eq(false, pcall_result)
+ end)
+ end)
+end)