diff options
Diffstat (limited to 'test/functional/lua')
-rw-r--r-- | test/functional/lua/buffer_updates_spec.lua | 333 | ||||
-rw-r--r-- | test/functional/lua/commands_spec.lua | 2 | ||||
-rw-r--r-- | test/functional/lua/luaeval_spec.lua | 4 | ||||
-rw-r--r-- | test/functional/lua/treesitter_spec.lua | 326 | ||||
-rw-r--r-- | test/functional/lua/vim_spec.lua | 375 |
5 files changed, 906 insertions, 134 deletions
diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index 77f8189bb9..7e4de7c39a 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -3,10 +3,14 @@ local helpers = require('test.functional.helpers')(after_each) local command = helpers.command local meths = helpers.meths +local funcs = helpers.funcs local clear = helpers.clear local eq = helpers.eq +local fail = helpers.fail local exec_lua = helpers.exec_lua local feed = helpers.feed +local deepcopy = helpers.deepcopy +local expect_events = helpers.expect_events local origlines = {"original line 1", "original line 2", @@ -16,32 +20,37 @@ local origlines = {"original line 1", "original line 6", " indented line"} -describe('lua: buffer event callbacks', function() - before_each(function() - clear() - exec_lua([[ - local events = {} +local function attach_buffer(evname) + exec_lua([[ + local evname = ... + local events = {} - function test_register(bufnr, id, changedtick, utf_sizes) - local function callback(...) - table.insert(events, {id, ...}) - if test_unreg == id then - return true - end + function test_register(bufnr, id, changedtick, utf_sizes) + local function callback(...) + table.insert(events, {id, ...}) + if test_unreg == id then + return true end - local opts = {on_lines=callback, on_detach=callback, utf_sizes=utf_sizes} - if changedtick then - opts.on_changedtick = callback - end - vim.api.nvim_buf_attach(bufnr, false, opts) end - - function get_events() - local ret_events = events - events = {} - return ret_events + local opts = {[evname]=callback, on_detach=callback, utf_sizes=utf_sizes} + if changedtick then + opts.on_changedtick = callback end - ]]) + vim.api.nvim_buf_attach(bufnr, false, opts) + end + + function get_events() + local ret_events = events + events = {} + return ret_events + end + ]], evname) +end + +describe('lua buffer event callbacks: on_lines', function() + before_each(function() + clear() + attach_buffer('on_lines') end) @@ -62,7 +71,7 @@ describe('lua: buffer event callbacks', function() local function check_events(expected) local events = exec_lua("return get_events(...)" ) if utf_sizes then - -- this test case uses ASCII only, so sizes sshould be the same. + -- this test case uses ASCII only, so sizes should be the same. -- Unicode is tested below. for _, event in ipairs(expected) do event[9] = event[8] @@ -216,4 +225,282 @@ describe('lua: buffer event callbacks', function() eq(1, meths.get_var('listener_cursor_line')) end) + it('does not SEGFAULT when calling win_findbuf in on_detach', function() + + exec_lua[[ + local buf = vim.api.nvim_create_buf(false, false) + + vim.cmd"split" + vim.api.nvim_win_set_buf(0, buf) + + vim.api.nvim_buf_attach(buf, false, { + on_detach = function(_, buf) + vim.fn.win_findbuf(buf) + end + }) + ]] + + command("q!") + helpers.assert_alive() + end) + + it('#12718 lnume', function() + meths.buf_set_lines(0, 0, -1, true, {'1', '2', '3'}) + exec_lua([[ + vim.api.nvim_buf_attach(0, false, { + on_lines = function(...) + vim.api.nvim_set_var('linesev', { ... }) + end, + }) + ]]) + feed('1G0') + feed('y<C-v>2j') + feed('G0') + feed('p') + -- Is the last arg old_byte_size correct? Doesn't matter for this PR + eq(meths.get_var('linesev'), { "lines", 1, 4, 2, 3, 5, 4 }) + + feed('2G0') + feed('p') + eq(meths.get_var('linesev'), { "lines", 1, 5, 1, 4, 4, 8 }) + + feed('1G0') + feed('P') + eq(meths.get_var('linesev'), { "lines", 1, 6, 0, 3, 3, 9 }) + + end) +end) + +describe('lua: nvim_buf_attach on_bytes', function() + before_each(function() + clear() + attach_buffer('on_bytes') + end) + + -- verifying the sizes with nvim_buf_get_offset is nice (checks we cannot + -- assert the wrong thing), but masks errors with unflushed lines (as + -- nvim_buf_get_offset forces a flush of the memline). To be safe run the + -- test both ways. + local function setup_eventcheck(verify, start_txt) + meths.buf_set_lines(0, 0, -1, true, start_txt) + local shadow = deepcopy(start_txt) + local shadowbytes = table.concat(shadow, '\n') .. '\n' + -- TODO: while we are brewing the real strong coffe, + -- verify should check buf_get_offset after every check_events + if verify then + meths.buf_get_offset(0, meths.buf_line_count(0)) + end + exec_lua("return test_register(...)", 0, "test1",false, nil) + meths.buf_get_changedtick(0) + + local verify_name = "test1" + local function check_events(expected) + local events = exec_lua("return get_events(...)" ) + expect_events(expected, events, "byte updates") + + if not verify then + return + end + + for _, event in ipairs(events) do + for _, elem in ipairs(event) do + if type(elem) == "number" and elem < 0 then + fail(string.format("Received event has negative values")) + end + end + + if event[1] == verify_name and event[2] == "bytes" then + local _, _, _, _, _, _, start_byte, _, _, old_byte, _, _, new_byte = unpack(event) + local before = string.sub(shadowbytes, 1, start_byte) + -- no text in the tests will contain 0xff bytes (invalid UTF-8) + -- so we can use it as marker for unknown bytes + local unknown = string.rep('\255', new_byte) + local after = string.sub(shadowbytes, start_byte + old_byte + 1) + shadowbytes = before .. unknown .. after + end + end + + local text = meths.buf_get_lines(0, 0, -1, true) + local bytes = table.concat(text, '\n') .. '\n' + + eq(string.len(bytes), string.len(shadowbytes), '\non_bytes: total bytecount of buffer is wrong') + for i = 1, string.len(shadowbytes) do + local shadowbyte = string.sub(shadowbytes, i, i) + if shadowbyte ~= '\255' then + eq(string.sub(bytes, i, i), shadowbyte, i) + end + end + end + + return check_events + end + + -- Yes, we can do both + local function do_both(verify) + it('single and multiple join', function() + local check_events = setup_eventcheck(verify, origlines) + feed 'ggJ' + check_events { + {'test1', 'bytes', 1, 3, 0, 15, 15, 1, 0, 1, 0, 1, 1}; + } + + feed '3J' + check_events { + {'test1', 'bytes', 1, 5, 0, 31, 31, 1, 0, 1, 0, 1, 1}; + {'test1', 'bytes', 1, 5, 0, 47, 47, 1, 0, 1, 0, 1, 1}; + } + end) + + it('opening lines', function() + local check_events = setup_eventcheck(verify, origlines) + -- meths.buf_set_option(0, 'autoindent', true) + feed 'Go' + check_events { + { "test1", "bytes", 1, 4, 7, 0, 114, 0, 0, 0, 1, 0, 1 }; + } + feed '<cr>' + check_events { + { "test1", "bytes", 1, 5, 7, 0, 114, 0, 0, 0, 1, 0, 1 }; + } + end) + + it('opening lines with autoindent', function() + local check_events = setup_eventcheck(verify, origlines) + meths.buf_set_option(0, 'autoindent', true) + feed 'Go' + check_events { + { "test1", "bytes", 1, 4, 7, 0, 114, 0, 0, 0, 1, 0, 5 }; + } + feed '<cr>' + check_events { + { "test1", "bytes", 1, 4, 8, 0, 115, 0, 4, 4, 0, 0, 0 }; + { "test1", "bytes", 1, 5, 7, 4, 118, 0, 0, 0, 1, 4, 5 }; + } + end) + + it('setline(num, line)', function() + local check_events = setup_eventcheck(verify, origlines) + funcs.setline(2, "babla") + check_events { + { "test1", "bytes", 1, 3, 1, 0, 16, 0, 15, 15, 0, 5, 5 }; + } + + funcs.setline(2, {"foo", "bar"}) + check_events { + { "test1", "bytes", 1, 4, 1, 0, 16, 0, 5, 5, 0, 3, 3 }; + { "test1", "bytes", 1, 5, 2, 0, 20, 0, 15, 15, 0, 3, 3 }; + } + + local buf_len = meths.buf_line_count(0) + funcs.setline(buf_len + 1, "baz") + check_events { + { "test1", "bytes", 1, 6, 7, 0, 90, 0, 0, 0, 1, 0, 4 }; + } + end) + + it('continuing comments with fo=or', function() + local check_events = setup_eventcheck(verify, {'// Comment'}) + meths.buf_set_option(0, 'formatoptions', 'ro') + meths.buf_set_option(0, 'filetype', 'c') + feed 'A<CR>' + check_events { + { "test1", "bytes", 1, 4, 0, 10, 10, 0, 0, 0, 1, 3, 4 }; + } + + feed '<ESC>' + check_events { + { "test1", "bytes", 1, 4, 1, 2, 13, 0, 1, 1, 0, 0, 0 }; + } + + feed 'ggo' -- goto first line to continue testing + check_events { + { "test1", "bytes", 1, 6, 1, 0, 11, 0, 0, 0, 1, 0, 4 }; + } + + feed '<CR>' + check_events { + { "test1", "bytes", 1, 6, 2, 2, 16, 0, 1, 1, 0, 0, 0 }; + { "test1", "bytes", 1, 7, 1, 3, 14, 0, 0, 0, 1, 3, 4 }; + } + end) + + it('editing empty buffers', function() + local check_events = setup_eventcheck(verify, {}) + + feed 'ia' + check_events { + { "test1", "bytes", 1, 3, 0, 0, 0, 0, 0, 0, 0, 1, 1 }; + } + end) + + it("changing lines", function() + local check_events = setup_eventcheck(verify, origlines) + + feed "cc" + check_events { + { "test1", "bytes", 1, 4, 0, 0, 0, 0, 15, 15, 0, 0, 0 }; + } + + feed "<ESC>" + check_events {} + + feed "c3j" + check_events { + { "test1", "bytes", 1, 4, 1, 0, 1, 3, 0, 48, 0, 0, 0 }; + } + end) + + it("visual charwise paste", function() + local check_events = setup_eventcheck(verify, {'1234567890'}) + funcs.setreg('a', '___') + + feed '1G1|vll' + check_events {} + + feed '"ap' + check_events { + { "test1", "bytes", 1, 3, 0, 0, 0, 0, 3, 3, 0, 0, 0 }; + { "test1", "bytes", 1, 5, 0, 0, 0, 0, 0, 0, 0, 3, 3 }; + } + end) + + it('blockwise paste', function() + local check_events = setup_eventcheck(verify, {'1', '2', '3'}) + feed('1G0') + feed('y<C-v>2j') + feed('G0') + feed('p') + check_events { + { "test1", "bytes", 1, 3, 2, 1, 5, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 3, 3, 0, 7, 0, 0, 0, 0, 3, 3 }; + { "test1", "bytes", 1, 3, 4, 0, 10, 0, 0, 0, 0, 3, 3 }; + } + + feed('2G0') + feed('p') + check_events { + { "test1", "bytes", 1, 4, 1, 1, 3, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 4, 2, 1, 6, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 4, 3, 1, 10, 0, 0, 0, 0, 1, 1 }; + } + + feed('1G0') + feed('P') + check_events { + { "test1", "bytes", 1, 5, 0, 0, 0, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 5, 1, 0, 3, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 5, 2, 0, 7, 0, 0, 0, 0, 1, 1 }; + } + + end) + end + + describe('(with verify) handles', function() + do_both(true) + end) + + describe('(without verify) handles', function() + do_both(false) + end) end) + diff --git a/test/functional/lua/commands_spec.lua b/test/functional/lua/commands_spec.lua index cbc3aee557..f2a1b7dede 100644 --- a/test/functional/lua/commands_spec.lua +++ b/test/functional/lua/commands_spec.lua @@ -43,7 +43,7 @@ describe(':lua command', function() eq({'', 'ETTS', 'TTSE', 'STTE'}, curbufmeths.get_lines(0, 100, false)) end) it('throws catchable errors', function() - eq([[Vim(lua):E5107: Error loading lua [string ":lua"]:1: unexpected symbol near ')']], + eq([[Vim(lua):E5107: Error loading lua [string ":lua"]:0: unexpected symbol near ')']], pcall_err(command, 'lua ()')) eq([[Vim(lua):E5108: Error executing lua [string ":lua"]:1: TEST]], exc_exec('lua error("TEST")')) diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua index 75966393b1..2ec48777fd 100644 --- a/test/functional/lua/luaeval_spec.lua +++ b/test/functional/lua/luaeval_spec.lua @@ -477,14 +477,14 @@ describe('v:lua', function() 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)", + eq("Vim:E5108: Error executing lua [string \"<nvim>\"]:0: 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)", + eq("Vim(call):E5108: Error executing lua [string \"<nvim>\"]:0: attempt to call global 'nonexistent' (a nil value)", pcall_err(command, 'call v:lua.mymod.crashy()')) end) diff --git a/test/functional/lua/treesitter_spec.lua b/test/functional/lua/treesitter_spec.lua index aa3d55b06d..3526b64395 100644 --- a/test/functional/lua/treesitter_spec.lua +++ b/test/functional/lua/treesitter_spec.lua @@ -15,17 +15,16 @@ 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')")) + eq("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", + pcall_err(exec_lua, "parser = vim.treesitter.get_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", + eq("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", pcall_err(exec_lua, "parser = vim.treesitter.inspect_language('borklang')")) end) - end) describe('treesitter API with C parser', function() @@ -127,6 +126,58 @@ void ui_refresh(void) } }]] + it('allows to iterate over nodes children', function() + if not check_parser() then return end + + insert(test_text); + + local res = exec_lua([[ + parser = vim.treesitter.get_parser(0, "c") + + func_node = parser:parse():root():child(0) + + res = {} + for node, field in func_node:iter_children() do + table.insert(res, {node:type(), field}) + end + return res + ]]) + + eq({ + {"primitive_type", "type"}, + {"function_declarator", "declarator"}, + {"compound_statement", "body"} + }, res) + end) + + it('allows to get a child by field', function() + if not check_parser() then return end + + insert(test_text); + + local res = exec_lua([[ + parser = vim.treesitter.get_parser(0, "c") + + func_node = parser:parse():root():child(0) + + local res = {} + for _, node in ipairs(func_node:field("type")) do + table.insert(res, {node:type(), node:range()}) + end + return res + ]]) + + eq({{ "primitive_type", 0, 0, 0, 4 }}, res) + + local res_fail = exec_lua([[ + parser = vim.treesitter.get_parser(0, "c") + + return #func_node:field("foo") == 0 + ]]) + + assert(res_fail) + end) + local query = [[ ((call_expression function: (identifier) @minfunc (argument_list (identifier) @min_id)) (eq? @minfunc "MIN")) "for" @keyword @@ -134,6 +185,16 @@ void ui_refresh(void) (field_expression argument: (identifier) @fieldarg) ]] + it("supports runtime queries", function() + if not check_parser() then return end + + local ret = exec_lua [[ + return require"vim.treesitter.query".get_query("c", "highlights").captures[1] + ]] + + eq('variable', ret) + end) + it('support query and iter by capture', function() if not check_parser() then return end @@ -198,6 +259,82 @@ void ui_refresh(void) }, res) end) + it('allow loading query with escaped quotes and capture them with `lua-match?` and `vim-match?`', function() + if not check_parser() then return end + + insert('char* astring = "Hello World!";') + + local res = exec_lua([[ + cquery = vim.treesitter.parse_query("c", '((_) @quote (vim-match? @quote "^\\"$")) ((_) @quote (lua-match? @quote "^\\"$"))') + parser = vim.treesitter.get_parser(0, "c") + tree = parser:parse() + res = {} + for pattern, match in cquery:iter_matches(tree:root(), 0, 0, 1) 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 + ]]) + + eq({ + { 1, { { "quote", '"', 0, 16, 0, 17 } } }, + { 2, { { "quote", '"', 0, 16, 0, 17 } } }, + { 1, { { "quote", '"', 0, 29, 0, 30 } } }, + { 2, { { "quote", '"', 0, 29, 0, 30 } } }, + }, res) + end) + + it('allows to add predicates', function() + insert([[ + int main(void) { + return 0; + } + ]]) + + local custom_query = "((identifier) @main (#is-main? @main))" + + local res = exec_lua([[ + local query = require"vim.treesitter.query" + + local function is_main(match, pattern, bufnr, predicate) + local node = match[ predicate[2] ] + + return query.get_node_text(node, bufnr) + end + + local parser = vim.treesitter.get_parser(0, "c") + + query.add_predicate("is-main?", is_main) + + local query = query.parse_query("c", ...) + + local nodes = {} + for _, node in query:iter_captures(parser:parse():root(), 0, 0, 19) do + table.insert(nodes, {node:range()}) + end + + return nodes + ]], custom_query) + + eq({{0, 4, 0, 8}}, res) + + local res_list = exec_lua[[ + local query = require'vim.treesitter.query' + + local list = query.list_predicates() + + table.sort(list) + + return list + ]] + + eq({ 'contains?', 'eq?', 'is-main?', 'lua-match?', 'match?', 'vim-match?' }, res_list) + end) + it('supports highlighting', function() if not check_parser() then return end @@ -243,10 +380,10 @@ static int nlua_schedule(lua_State *const lstate) (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_]\+$")) +; Use lua regexes +((identifier) @Identifier (#contains? @Identifier "lua_")) +((identifier) @Constant (#lua-match? @Constant "^[A-Z_]+$")) +((identifier) @Normal (#vim-match? @Constant "^lstate$")) ((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right) (#eq? @WarningMsg.left @WarningMsg.right)) @@ -292,13 +429,14 @@ static int nlua_schedule(lua_State *const lstate) ]]} exec_lua([[ - local TSHighlighter = vim.treesitter.TSHighlighter + local parser = vim.treesitter.get_parser(0, "c") + local highlighter = vim.treesitter.highlighter local query = ... - test_hl = TSHighlighter.new(query, 0, "c") + test_hl = highlighter.new(parser, query) ]], 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) | + {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | { | {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | || {6:lstate} != {6:lstate}) { | @@ -306,9 +444,9 @@ static int nlua_schedule(lua_State *const lstate) {4:return} {11:lua_error}(lstate); | } | | - {7:LuaRef} cb = nlua_ref(lstate, {5:1}); | + {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | | - multiqueue_put(main_loop.events, nlua_schedule_event, | + multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | {4:return} {5:0}; | ^} | @@ -317,10 +455,33 @@ static int nlua_schedule(lua_State *const lstate) | ]]} + feed("5Goc<esc>dd") + + screen:expect{grid=[[ + {2:/// Schedule Lua callback on main loop's event queue} | + {3:static} {3:int} {11: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 = {11:nlua_ref}(lstate, {5:1}); | + | + multiqueue_put(main_loop.events, {11: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) | + {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | { | {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | || {6:lstate} != {6:lstate}) { | @@ -329,9 +490,9 @@ static int nlua_schedule(lua_State *const lstate) {8:*^/} | } | | - {7:LuaRef} cb = nlua_ref(lstate, {5:1}); | + {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | | - multiqueue_put(main_loop.events, nlua_schedule_event, | + multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | {4:return} {5:0}; | } | @@ -342,7 +503,7 @@ static int nlua_schedule(lua_State *const lstate) 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) | + {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | { | {2:/^*} | {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} | @@ -352,9 +513,9 @@ static int nlua_schedule(lua_State *const lstate) {2:*/} | } | | - {7:LuaRef} cb = nlua_ref(lstate, {5:1}); | + {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | | - multiqueue_put(main_loop.events, nlua_schedule_event, | + multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | {4:return} {5:0}; | {8:}} | @@ -365,7 +526,7 @@ static int nlua_schedule(lua_State *const lstate) 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) | + {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | { | {2:/*} | {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} | @@ -375,9 +536,9 @@ static int nlua_schedule(lua_State *const lstate) {2:*/} | } | | - {7:LuaRef} cb = nlua_ref(lstate, {5:1}); | + {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | | - multiqueue_put(main_loop.events, nlua_schedule_event, | + multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | {4:return} {5:0}; | {8:}} | @@ -388,7 +549,7 @@ static int nlua_schedule(lua_State *const lstate) 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) | + {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | { | {2:/*} | {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} | @@ -398,9 +559,9 @@ static int nlua_schedule(lua_State *const lstate) {2:*/} | } | | - {7:LuaRef} cb = nlua_ref(lstate, {5:1}); | + {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | | - multiqueue_put(main_loop.events, nlua_schedule_event, | + multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | {4:return} {5:0}; | {8:}} | @@ -408,6 +569,72 @@ static int nlua_schedule(lua_State *const lstate) ]]} end) + it("supports highlighting with custom parser", function() + if not check_parser() then return end + + local screen = Screen.new(65, 18) + screen:attach() + screen:set_default_attr_ids({ {bold = true, foreground = Screen.colors.SeaGreen4} }) + + insert(test_text) + + screen:expect{ grid= [[ + 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); | + } | + } | + ^} | + | + ]] } + + 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 hl = vim.treesitter.highlighter.new(parser, "(identifier) @type") + ]]) + + screen:expect{ grid = [[ + int {1:width} = {1:INT_MAX}, {1:height} = {1:INT_MAX}; | + bool {1:ext_widgets}[{1:kUIExtCount}]; | + for (UIExtension {1:i} = 0; (int)i < kUIExtCount; i++) { | + ext_widgets[i] = true; | + } | + | + bool {1:inclusive} = {1:ui_override}(); | + for (size_t {1:i} = 0; i < ui_count; i++) { | + UI *{1:ui} = {1:uis}[{1:i}]; | + width = MIN(ui->width, width); | + height = MIN(ui->height, height); | + foo = BAR(ui->bazaar, bazaar); | + for (UIExtension {1:j} = 0; (int)j < kUIExtCount; j++) { | + ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + } | + } | + ^} | + | + ]] } + end) + it('inspects language', function() if not check_parser() then return end @@ -453,23 +680,29 @@ static int nlua_schedule(lua_State *const lstate) insert(test_text) - local res = exec_lua([[ + 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 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) + + local range = exec_lua [[ + return parser:included_ranges() + ]] + + eq(range, { { 0, 0, 18, 1 } }) end) it("allows to set complex ranges", function() if not check_parser() then return end @@ -477,7 +710,7 @@ static int nlua_schedule(lua_State *const lstate) insert(test_text) - local res = exec_lua([[ + local res = exec_lua [[ parser = vim.treesitter.get_parser(0, "c") query = vim.treesitter.parse_query("c", "(declaration) @decl") @@ -495,7 +728,7 @@ static int nlua_schedule(lua_State *const lstate) table.insert(res, { root:named_child(i):range() }) end return res - ]]) + ]] eq({ { 2, 2, 2, 40 }, @@ -509,4 +742,35 @@ static int nlua_schedule(lua_State *const lstate) { 10, 5, 10, 20 }, { 14, 9, 14, 27 } }, res) end) + + it("allows to create string parsers", function() + local ret = exec_lua [[ + local parser = vim.treesitter.get_string_parser("int foo = 42;", "c") + return { parser:parse():root():range() } + ]] + + eq({ 0, 0, 0, 13 }, ret) + end) + + it("allows to run queries with string parsers", function() + local txt = [[ + int foo = 42; + int bar = 13; + ]] + + local ret = exec_lua([[ + local str = ... + local parser = vim.treesitter.get_string_parser(str, "c") + + local nodes = {} + local query = vim.treesitter.parse_query("c", '((identifier) @id (eq? @id "foo"))') + + for _, node in query:iter_captures(parser:parse():root(), str, 0, 2) do + table.insert(nodes, { node:range() }) + end + + return nodes]], txt) + + eq({ {0, 10, 0, 13} }, ret) + end) end) diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 9b2697b3c2..1cbf6b21e3 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -4,13 +4,14 @@ local Screen = require('test.functional.ui.screen') local funcs = helpers.funcs local meths = helpers.meths +local dedent = helpers.dedent 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 pcall_err_withfile = helpers.pcall_err_withfile local exec_lua = helpers.exec_lua local matches = helpers.matches local source = helpers.source @@ -128,8 +129,8 @@ describe('lua stdlib', function() 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")'))) + eq("string", type(pcall_err_withfile(funcs.luaeval, 'vim.startswith("123", nil)'))) + eq("string", type(pcall_err_withfile(funcs.luaeval, 'vim.startswith(nil, "123")'))) end) it('vim.endswith', function() @@ -142,8 +143,8 @@ describe('lua stdlib', function() 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")'))) + eq("string", type(pcall_err_withfile(funcs.luaeval, 'vim.endswith("123", nil)'))) + eq("string", type(pcall_err_withfile(funcs.luaeval, 'vim.endswith(nil, "123")'))) end) it("vim.str_utfindex/str_byteindex", function() @@ -182,10 +183,10 @@ describe('lua stdlib', function() 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()")) + eq('.../helpers.lua:0: Error executing lua: vim.schedule: expected function', + pcall_err_withfile(exec_lua, "vim.schedule('stringly')")) + eq('.../helpers.lua:0: Error executing lua: vim.schedule: expected function', + pcall_err_withfile(exec_lua, "vim.schedule()")) exec_lua([[ vim.schedule(function() @@ -257,17 +258,29 @@ describe('lua stdlib', function() } for _, t in ipairs(loops) do - matches(".*Infinite loop detected", pcall_err(split, t[1], t[2])) + matches(".*Infinite loop detected", pcall_err_withfile(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)) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: shared.lua:0: s: expected string, got number + stack traceback: + shared.lua:0: in function 'gsplit' + shared.lua:0: in function <shared.lua:0>]]), + pcall_err_withfile(split, 1, 'string')) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: shared.lua:0: sep: expected string, got number + stack traceback: + shared.lua:0: in function 'gsplit' + shared.lua:0: in function <shared.lua:0>]]), + pcall_err_withfile(split, 'string', 1)) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: shared.lua:0: plain: expected boolean, got number + stack traceback: + shared.lua:0: in function 'gsplit' + shared.lua:0: in function <shared.lua:0>]]), + pcall_err_withfile(split, 'string', 'string', 1)) end) it('vim.trim', function() @@ -287,8 +300,11 @@ describe('lua stdlib', function() end -- Validates args. - eq('Error executing lua: .../shared.lua: s: expected string, got number', - pcall_err(trim, 2)) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: shared.lua:0: s: expected string, got number + stack traceback: + shared.lua:0: in function <shared.lua:0>]]), + pcall_err_withfile(trim, 2)) end) it('vim.inspect', function() @@ -353,8 +369,8 @@ describe('lua stdlib', function() return t1.f() ~= t2.f() ]])) - eq('Error executing lua: .../shared.lua: Cannot deepcopy object of type thread', - pcall_err(exec_lua, [[ + eq('.../helpers.lua:0: Error executing lua: shared.lua:0: Cannot deepcopy object of type thread', + pcall_err_withfile(exec_lua, [[ local thread = coroutine.create(function () return 0 end) local t = {thr = thread} vim.deepcopy(t) @@ -366,8 +382,11 @@ describe('lua stdlib', function() 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)]])) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: shared.lua:0: s: expected string, got number + stack traceback: + shared.lua:0: in function <shared.lua:0>]]), + pcall_err_withfile(exec_lua, [[return vim.pesc(2)]])) end) it('vim.tbl_keys', function() @@ -491,20 +510,20 @@ describe('lua stdlib', function() 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, [[ + eq('.../helpers.lua:0: Error executing lua: shared.lua:0: invalid "behavior": nil', + pcall_err_withfile(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, [[ + eq('.../helpers.lua:0: Error executing lua: shared.lua:0: wrong number of arguments (given 1, expected at least 3)', + pcall_err_withfile(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, [[ + eq('.../helpers.lua:0: Error executing lua: shared.lua:0: wrong number of arguments (given 2, expected at least 3)', + pcall_err_withfile(exec_lua, [[ return vim.tbl_extend("keep", {}) ]]) ) @@ -579,20 +598,20 @@ describe('lua stdlib', function() return vim.tbl_islist(c) and count == 0 ]])) - eq('Error executing lua: .../shared.lua: invalid "behavior": nil', - pcall_err(exec_lua, [[ + eq('.../helpers.lua:0: Error executing lua: shared.lua:0: invalid "behavior": nil', + pcall_err_withfile(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, [[ + eq('.../helpers.lua:0: Error executing lua: shared.lua:0: wrong number of arguments (given 1, expected at least 3)', + pcall_err_withfile(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, [[ + eq('.../helpers.lua:0: Error executing lua: shared.lua:0: wrong number of arguments (given 2, expected at least 3)', + pcall_err_withfile(exec_lua, [[ return vim.tbl_deep_extend("keep", {}) ]]) ) @@ -624,8 +643,11 @@ describe('lua stdlib', function() 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(dedent([[ + .../helpers.lua:0: Error executing lua: shared.lua:0: src: expected table, got nil + stack traceback: + shared.lua:0: in function <shared.lua:0>]]), + pcall_err_withfile(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) ]]) @@ -648,8 +670,8 @@ describe('lua stdlib', function() 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)) + matches('.../helpers.lua:0: Error executing lua: shared.lua:0: The reverse lookup found an existing value for "[1A]" while processing key "[1A]"', + pcall_err_withfile(exec_lua, code)) end) it('vim.call, vim.fn', function() @@ -820,34 +842,77 @@ describe('lua stdlib', function() 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 }}")) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: opt[1]: expected table, got number + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{ 1, 'x' }")) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: invalid type name: x + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{ arg1={ 1, 'x' }}")) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: invalid type name: 1 + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{ arg1={ 1, 1 }}")) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: invalid type name: nil + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(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' }}")) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: arg1: expected string, got nil + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(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}}")) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: arg1: expected string, got nil + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{ arg1={ nil, 's', false }}")) + + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: arg1: expected table, got number + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{arg1={1, 't'}}")) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: arg2: expected string, got number + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={1, 's'}}")) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: arg2: expected string, got nil + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={nil, 's'}}")) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: arg2: expected string, got nil + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={nil, 's'}}")) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: arg1: expected even number, got 3 + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{arg1={3, function(a) return a == 1 end, 'even number'}}")) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: arg1: expected ?, got 3 + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{arg1={3, function(a) return a == 1 end}}")) + + -- Pass an additional message back. + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: arg1: expected ?, got 3. Info: TEST_MSG + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{arg1={3, function(a) return a == 1, 'TEST_MSG' end}}")) end) it('vim.is_callable', function() @@ -992,10 +1057,10 @@ describe('lua stdlib', function() ]] 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')) + matches("^.../helpers.lua:0: Error executing lua: .*: Invalid option name: 'nosuchopt'$", + pcall_err_withfile(exec_lua, 'return vim.bo.nosuchopt')) + matches("^.../helpers.lua:0: Error executing lua: .*: Expected lua string$", + pcall_err_withfile(exec_lua, 'return vim.bo[0][0].autoread')) end) it('vim.wo', function() @@ -1011,10 +1076,10 @@ describe('lua stdlib', function() 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')) + matches("^.../helpers.lua:0: Error executing lua: .*: Invalid option name: 'notanopt'$", + pcall_err_withfile(exec_lua, 'return vim.wo.notanopt')) + matches("^.../helpers.lua:0: Error executing lua: .*: Expected lua string$", + pcall_err_withfile(exec_lua, 'return vim.wo[0][0].list')) eq(2, funcs.luaeval "vim.wo[1000].cole") exec_lua [[ vim.wo[1000].cole = 0 @@ -1068,6 +1133,104 @@ describe('lua stdlib', function() eq({5,15}, exec_lua[[ return vim.region(0,{1,5},{1,14},'v',true)[1] ]]) end) + describe('vim.execute_on_keystroke', function() + it('should keep track of keystrokes', function() + helpers.insert([[hello world ]]) + + exec_lua [[ + KeysPressed = {} + + vim.register_keystroke_callback(function(buf) + if buf:byte() == 27 then + buf = "<ESC>" + end + + table.insert(KeysPressed, buf) + end) + ]] + + helpers.insert([[next 🤦 lines å ]]) + + -- It has escape in the keys pressed + eq('inext 🤦 lines å <ESC>', exec_lua [[return table.concat(KeysPressed, '')]]) + end) + + it('should allow removing trackers.', function() + helpers.insert([[hello world]]) + + exec_lua [[ + KeysPressed = {} + + return vim.register_keystroke_callback(function(buf) + if buf:byte() == 27 then + buf = "<ESC>" + end + + table.insert(KeysPressed, buf) + end, vim.api.nvim_create_namespace("logger")) + ]] + + helpers.insert([[next lines]]) + + exec_lua("vim.register_keystroke_callback(nil, vim.api.nvim_create_namespace('logger'))") + + helpers.insert([[more lines]]) + + -- It has escape in the keys pressed + eq('inext lines<ESC>', exec_lua [[return table.concat(KeysPressed, '')]]) + end) + + it('should not call functions that error again.', function() + helpers.insert([[hello world]]) + + exec_lua [[ + KeysPressed = {} + + return vim.register_keystroke_callback(function(buf) + if buf:byte() == 27 then + buf = "<ESC>" + end + + table.insert(KeysPressed, buf) + + if buf == 'l' then + error("Dumb Error") + end + end) + ]] + + helpers.insert([[next lines]]) + helpers.insert([[more lines]]) + + -- Only the first letter gets added. After that we remove the callback + eq('inext l', exec_lua [[ return table.concat(KeysPressed, '') ]]) + end) + + it('should process mapped keys, not unmapped keys', function() + exec_lua [[ + KeysPressed = {} + + vim.cmd("inoremap hello world") + + vim.register_keystroke_callback(function(buf) + if buf:byte() == 27 then + buf = "<ESC>" + end + + table.insert(KeysPressed, buf) + end) + ]] + + helpers.insert("hello") + + local next_status = exec_lua [[ + return table.concat(KeysPressed, '') + ]] + + eq("iworld<ESC>", next_status) + end) + end) + describe('vim.wait', function() before_each(function() exec_lua[[ @@ -1116,6 +1279,23 @@ describe('lua stdlib', function() ]]) end) + it('should not process non-fast events when commanded', function() + eq({wait_result = false}, 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)) + + wait_result = vim.wait(300, function() return vim.g.timer_result end, nil, true) + + return { + 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() @@ -1130,22 +1310,38 @@ describe('lua stdlib', function() ]]) end) - it('should require functions to be passed', function() + it('should not crash when callback errors', function() local pcall_result = exec_lua [[ - return {pcall(function() vim.wait(1000, 13) end)} + return {pcall(function() vim.wait(1000, function() error("As Expected") end) end)} ]] eq(pcall_result[1], false) - matches('condition must be a function', pcall_result[2]) + matches('As Expected', pcall_result[2]) end) - it('should not crash when callback errors', function() + it('if callback is passed, it must be a function', function() local pcall_result = exec_lua [[ - return {pcall(function() vim.wait(1000, function() error("As Expected") end) end)} + return {pcall(function() vim.wait(1000, 13) end)} ]] eq(pcall_result[1], false) - matches('As Expected', pcall_result[2]) + matches('if passed, condition must be a function', pcall_result[2]) + end) + + it('should allow waiting with no callback, explicit', function() + eq(true, exec_lua [[ + local start_time = vim.loop.hrtime() + vim.wait(50, nil) + return vim.loop.hrtime() - start_time > 25000 + ]]) + end) + + it('should allow waiting with no callback, implicit', function() + eq(true, exec_lua [[ + local start_time = vim.loop.hrtime() + vim.wait(50) + return vim.loop.hrtime() - start_time > 25000 + ]]) end) it('should call callbacks exactly once if they return true immediately', function() @@ -1232,4 +1428,29 @@ describe('lua stdlib', function() eq(false, pcall_result) end) end) + + describe('vim.api.nvim_buf_call', function() + it('can access buf options', function() + local buf1 = meths.get_current_buf() + local buf2 = exec_lua [[ + buf2 = vim.api.nvim_create_buf(false, true) + return buf2 + ]] + + eq(false, meths.buf_get_option(buf1, 'autoindent')) + eq(false, meths.buf_get_option(buf2, 'autoindent')) + + local val = exec_lua [[ + return vim.api.nvim_buf_call(buf2, function() + vim.cmd "set autoindent" + return vim.api.nvim_get_current_buf() + end) + ]] + + eq(false, meths.buf_get_option(buf1, 'autoindent')) + eq(true, meths.buf_get_option(buf2, 'autoindent')) + eq(buf1, meths.get_current_buf()) + eq(buf2, val) + end) + end) end) |