diff options
Diffstat (limited to 'test')
28 files changed, 3656 insertions, 751 deletions
diff --git a/test/functional/api/mark_extended_spec.lua b/test/functional/api/mark_extended_spec.lua new file mode 100644 index 0000000000..76db9f9d81 --- /dev/null +++ b/test/functional/api/mark_extended_spec.lua @@ -0,0 +1,1375 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local request = helpers.request +local eq = helpers.eq +local ok = helpers.ok +local curbufmeths = helpers.curbufmeths +local pcall_err = helpers.pcall_err +local insert = helpers.insert +local feed = helpers.feed +local clear = helpers.clear +local command = helpers.command + +local function check_undo_redo(ns, mark, sr, sc, er, ec) --s = start, e = end + local rv = curbufmeths.get_extmark_by_id(ns, mark) + eq({er, ec}, rv) + feed("u") + rv = curbufmeths.get_extmark_by_id(ns, mark) + eq({sr, sc}, rv) + feed("<c-r>") + rv = curbufmeths.get_extmark_by_id(ns, mark) + eq({er, ec}, rv) +end + +local function set_extmark(ns_id, id, line, col, opts) + if opts == nil then + opts = {} + end + return curbufmeths.set_extmark(ns_id, id, line, col, opts) +end + +local function get_extmarks(ns_id, start, end_, opts) + if opts == nil then + opts = {} + end + return curbufmeths.get_extmarks(ns_id, start, end_, opts) +end + +describe('Extmarks buffer api', function() + local screen + local marks, positions, ns_string2, ns_string, init_text, row, col + local ns, ns2 + + before_each(function() + -- Initialize some namespaces and insert 12345 into a buffer + marks = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12} + positions = {{0, 0,}, {0, 2}, {0, 3}} + + ns_string = "my-fancy-plugin" + ns_string2 = "my-fancy-plugin2" + init_text = "12345" + row = 0 + col = 2 + + clear() + screen = Screen.new(15, 10) + screen:attach() + + insert(init_text) + ns = request('nvim_create_namespace', ns_string) + ns2 = request('nvim_create_namespace', ns_string2) + end) + + it('adds, updates and deletes marks #extmarks', function() + local rv = set_extmark(ns, marks[1], positions[1][1], positions[1][2]) + eq(marks[1], rv) + rv = curbufmeths.get_extmark_by_id(ns, marks[1]) + eq({positions[1][1], positions[1][2]}, rv) + -- Test adding a second mark on same row works + rv = set_extmark(ns, marks[2], positions[2][1], positions[2][2]) + eq(marks[2], rv) + + -- Test an update, (same pos) + rv = set_extmark(ns, marks[1], positions[1][1], positions[1][2]) + eq(marks[1], rv) + rv = curbufmeths.get_extmark_by_id(ns, marks[2]) + eq({positions[2][1], positions[2][2]}, rv) + -- Test an update, (new pos) + row = positions[1][1] + col = positions[1][2] + 1 + rv = set_extmark(ns, marks[1], row, col) + eq(marks[1], rv) + rv = curbufmeths.get_extmark_by_id(ns, marks[1]) + eq({row, col}, rv) + + -- remove the test marks + eq(true, curbufmeths.del_extmark(ns, marks[1])) + eq(false, curbufmeths.del_extmark(ns, marks[1])) + eq(true, curbufmeths.del_extmark(ns, marks[2])) + eq(false, curbufmeths.del_extmark(ns, marks[3])) + eq(false, curbufmeths.del_extmark(ns, 1000)) + end) + + it('can clear a specific namespace range #extmarks', function() + set_extmark(ns, 1, 0, 1) + set_extmark(ns2, 1, 0, 1) + -- force a new undo buffer + feed('o<esc>') + curbufmeths.clear_namespace(ns2, 0, -1) + eq({{1, 0, 1}}, get_extmarks(ns, {0, 0}, {-1, -1})) + eq({}, get_extmarks(ns2, {0, 0}, {-1, -1})) + feed('u') + eq({{1, 0, 1}}, get_extmarks(ns, {0, 0}, {-1, -1})) + eq({{1, 0, 1}}, get_extmarks(ns2, {0, 0}, {-1, -1})) + feed('<c-r>') + eq({{1, 0, 1}}, get_extmarks(ns, {0, 0}, {-1, -1})) + eq({}, get_extmarks(ns2, {0, 0}, {-1, -1})) + end) + + it('can clear a namespace range using 0,-1 #extmarks', function() + set_extmark(ns, 1, 0, 1) + set_extmark(ns2, 1, 0, 1) + -- force a new undo buffer + feed('o<esc>') + curbufmeths.clear_namespace(-1, 0, -1) + eq({}, get_extmarks(ns, {0, 0}, {-1, -1})) + eq({}, get_extmarks(ns2, {0, 0}, {-1, -1})) + feed('u') + eq({{1, 0, 1}}, get_extmarks(ns, {0, 0}, {-1, -1})) + eq({{1, 0, 1}}, get_extmarks(ns2, {0, 0}, {-1, -1})) + feed('<c-r>') + eq({}, get_extmarks(ns, {0, 0}, {-1, -1})) + eq({}, get_extmarks(ns2, {0, 0}, {-1, -1})) + end) + + it('querying for information and ranges #extmarks', function() + -- add some more marks + for i, m in ipairs(marks) do + if positions[i] ~= nil then + local rv = set_extmark(ns, m, positions[i][1], positions[i][2]) + eq(m, rv) + end + end + + -- {0, 0} and {-1, -1} work as extreme values + eq({{1, 0, 0}}, get_extmarks(ns, {0, 0}, {0, 0})) + eq({}, get_extmarks(ns, {-1, -1}, {-1, -1})) + local rv = get_extmarks(ns, {0, 0}, {-1, -1}) + for i, m in ipairs(marks) do + if positions[i] ~= nil then + eq({m, positions[i][1], positions[i][2]}, rv[i]) + end + end + + -- 0 and -1 works as short hand extreme values + eq({{1, 0, 0}}, get_extmarks(ns, 0, 0)) + eq({}, get_extmarks(ns, -1, -1)) + rv = get_extmarks(ns, 0, -1) + for i, m in ipairs(marks) do + if positions[i] ~= nil then + eq({m, positions[i][1], positions[i][2]}, rv[i]) + end + end + + -- next with mark id + rv = get_extmarks(ns, marks[1], {-1, -1}, {amount=1}) + eq({{marks[1], positions[1][1], positions[1][2]}}, rv) + rv = get_extmarks(ns, marks[2], {-1, -1}, {amount=1}) + eq({{marks[2], positions[2][1], positions[2][2]}}, rv) + -- next with positional when mark exists at position + rv = get_extmarks(ns, positions[1], {-1, -1}, {amount=1}) + eq({{marks[1], positions[1][1], positions[1][2]}}, rv) + -- next with positional index (no mark at position) + rv = get_extmarks(ns, {positions[1][1], positions[1][2] +1}, {-1, -1}, {amount=1}) + eq({{marks[2], positions[2][1], positions[2][2]}}, rv) + -- next with Extremity index + rv = get_extmarks(ns, {0,0}, {-1, -1}, {amount=1}) + eq({{marks[1], positions[1][1], positions[1][2]}}, rv) + + -- nextrange with mark id + rv = get_extmarks(ns, marks[1], marks[3]) + eq({marks[1], positions[1][1], positions[1][2]}, rv[1]) + eq({marks[2], positions[2][1], positions[2][2]}, rv[2]) + -- nextrange with amount + rv = get_extmarks(ns, marks[1], marks[3], {amount=2}) + eq(2, table.getn(rv)) + -- nextrange with positional when mark exists at position + rv = get_extmarks(ns, positions[1], positions[3]) + eq({marks[1], positions[1][1], positions[1][2]}, rv[1]) + eq({marks[2], positions[2][1], positions[2][2]}, rv[2]) + rv = get_extmarks(ns, positions[2], positions[3]) + eq(2, table.getn(rv)) + -- nextrange with positional index (no mark at position) + local lower = {positions[1][1], positions[2][2] -1} + local upper = {positions[2][1], positions[3][2] - 1} + rv = get_extmarks(ns, lower, upper) + eq({{marks[2], positions[2][1], positions[2][2]}}, rv) + lower = {positions[3][1], positions[3][2] + 1} + upper = {positions[3][1], positions[3][2] + 2} + rv = get_extmarks(ns, lower, upper) + eq({}, rv) + -- nextrange with extremity index + lower = {positions[2][1], positions[2][2]+1} + upper = {-1, -1} + rv = get_extmarks(ns, lower, upper) + eq({{marks[3], positions[3][1], positions[3][2]}}, rv) + + -- prev with mark id + rv = get_extmarks(ns, marks[3], {0, 0}, {amount=1}) + eq({{marks[3], positions[3][1], positions[3][2]}}, rv) + rv = get_extmarks(ns, marks[2], {0, 0}, {amount=1}) + eq({{marks[2], positions[2][1], positions[2][2]}}, rv) + -- prev with positional when mark exists at position + rv = get_extmarks(ns, positions[3], {0, 0}, {amount=1}) + eq({{marks[3], positions[3][1], positions[3][2]}}, rv) + -- prev with positional index (no mark at position) + rv = get_extmarks(ns, {positions[1][1], positions[1][2] +1}, {0, 0}, {amount=1}) + eq({{marks[1], positions[1][1], positions[1][2]}}, rv) + -- prev with Extremity index + rv = get_extmarks(ns, {-1,-1}, {0,0}, {amount=1}) + eq({{marks[3], positions[3][1], positions[3][2]}}, rv) + + -- prevrange with mark id + rv = get_extmarks(ns, marks[3], marks[1]) + eq({marks[3], positions[3][1], positions[3][2]}, rv[1]) + eq({marks[2], positions[2][1], positions[2][2]}, rv[2]) + eq({marks[1], positions[1][1], positions[1][2]}, rv[3]) + -- prevrange with amount + rv = get_extmarks(ns, marks[3], marks[1], {amount=2}) + eq(2, table.getn(rv)) + -- prevrange with positional when mark exists at position + rv = get_extmarks(ns, positions[3], positions[1]) + eq({{marks[3], positions[3][1], positions[3][2]}, + {marks[2], positions[2][1], positions[2][2]}, + {marks[1], positions[1][1], positions[1][2]}}, rv) + rv = get_extmarks(ns, positions[2], positions[1]) + eq(2, table.getn(rv)) + -- prevrange with positional index (no mark at position) + lower = {positions[2][1], positions[2][2] + 1} + upper = {positions[3][1], positions[3][2] + 1} + rv = get_extmarks(ns, upper, lower) + eq({{marks[3], positions[3][1], positions[3][2]}}, rv) + lower = {positions[3][1], positions[3][2] + 1} + upper = {positions[3][1], positions[3][2] + 2} + rv = get_extmarks(ns, upper, lower) + eq({}, rv) + -- prevrange with extremity index + lower = {0,0} + upper = {positions[2][1], positions[2][2] - 1} + rv = get_extmarks(ns, upper, lower) + eq({{marks[1], positions[1][1], positions[1][2]}}, rv) + end) + + it('querying for information with amount #extmarks', function() + -- add some more marks + for i, m in ipairs(marks) do + if positions[i] ~= nil then + local rv = set_extmark(ns, m, positions[i][1], positions[i][2]) + eq(m, rv) + end + end + + local rv = get_extmarks(ns, {0, 0}, {-1, -1}, {amount=1}) + eq(1, table.getn(rv)) + rv = get_extmarks(ns, {0, 0}, {-1, -1}, {amount=2}) + eq(2, table.getn(rv)) + rv = get_extmarks(ns, {0, 0}, {-1, -1}, {amount=3}) + eq(3, table.getn(rv)) + + -- now in reverse + rv = get_extmarks(ns, {0, 0}, {-1, -1}, {amount=1}) + eq(1, table.getn(rv)) + rv = get_extmarks(ns, {0, 0}, {-1, -1}, {amount=2}) + eq(2, table.getn(rv)) + rv = get_extmarks(ns, {0, 0}, {-1, -1}, {amount=3}) + eq(3, table.getn(rv)) + end) + + it('get_marks works when mark col > upper col #extmarks', function() + feed('A<cr>12345<esc>') + feed('A<cr>12345<esc>') + set_extmark(ns, 10, 0, 2) -- this shouldn't be found + set_extmark(ns, 11, 2, 1) -- this shouldn't be found + set_extmark(ns, marks[1], 0, 4) -- check col > our upper bound + set_extmark(ns, marks[2], 1, 1) -- check col < lower bound + set_extmark(ns, marks[3], 2, 0) -- check is inclusive + eq({{marks[1], 0, 4}, + {marks[2], 1, 1}, + {marks[3], 2, 0}}, + get_extmarks(ns, {0, 3}, {2, 0})) + end) + + it('get_marks works in reverse when mark col < lower col #extmarks', function() + feed('A<cr>12345<esc>') + feed('A<cr>12345<esc>') + set_extmark(ns, 10, 0, 1) -- this shouldn't be found + set_extmark(ns, 11, 2, 4) -- this shouldn't be found + set_extmark(ns, marks[1], 2, 1) -- check col < our lower bound + set_extmark(ns, marks[2], 1, 4) -- check col > upper bound + set_extmark(ns, marks[3], 0, 2) -- check is inclusive + local rv = get_extmarks(ns, {2, 3}, {0, 2}) + eq({{marks[1], 2, 1}, + {marks[2], 1, 4}, + {marks[3], 0, 2}}, + rv) + end) + + it('get_marks amount 0 returns nothing #extmarks', function() + set_extmark(ns, marks[1], positions[1][1], positions[1][2]) + local rv = get_extmarks(ns, {-1, -1}, {-1, -1}, {amount=0}) + eq({}, rv) + end) + + + it('marks move with line insertations #extmarks', function() + set_extmark(ns, marks[1], 0, 0) + feed("yyP") + check_undo_redo(ns, marks[1], 0, 0, 1, 0) + end) + + it('marks move with multiline insertations #extmarks', function() + feed("a<cr>22<cr>33<esc>") + set_extmark(ns, marks[1], 1, 1) + feed('ggVGyP') + check_undo_redo(ns, marks[1], 1, 1, 4, 1) + end) + + it('marks move with line join #extmarks', function() + -- do_join in ops.c + feed("a<cr>222<esc>") + set_extmark(ns, marks[1], 1, 0) + feed('ggJ') + check_undo_redo(ns, marks[1], 1, 0, 0, 6) + end) + + it('join works when no marks are present #extmarks', function() + feed("a<cr>1<esc>") + feed('kJ') + -- This shouldn't seg fault + screen:expect([[ + 12345^ 1 | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + | + ]]) + end) + + it('marks move with multiline join #extmarks', function() + -- do_join in ops.c + feed("a<cr>222<cr>333<cr>444<esc>") + set_extmark(ns, marks[1], 3, 0) + feed('2GVGJ') + check_undo_redo(ns, marks[1], 3, 0, 1, 8) + end) + + it('marks move with line deletes #extmarks', function() + feed("a<cr>222<cr>333<cr>444<esc>") + set_extmark(ns, marks[1], 2, 1) + feed('ggjdd') + check_undo_redo(ns, marks[1], 2, 1, 1, 1) + end) + + it('marks move with multiline deletes #extmarks', function() + feed("a<cr>222<cr>333<cr>444<esc>") + set_extmark(ns, marks[1], 3, 0) + feed('gg2dd') + check_undo_redo(ns, marks[1], 3, 0, 1, 0) + -- regression test, undoing multiline delete when mark is on row 1 + feed('ugg3dd') + check_undo_redo(ns, marks[1], 3, 0, 0, 0) + end) + + it('marks move with open line #extmarks', function() + -- open_line in misc1.c + -- testing marks below are also moved + feed("yyP") + set_extmark(ns, marks[1], 0, 4) + set_extmark(ns, marks[2], 1, 4) + feed('1G<s-o><esc>') + check_undo_redo(ns, marks[1], 0, 4, 1, 4) + check_undo_redo(ns, marks[2], 1, 4, 2, 4) + feed('2Go<esc>') + check_undo_redo(ns, marks[1], 1, 4, 1, 4) + check_undo_redo(ns, marks[2], 2, 4, 3, 4) + end) + + it('marks move with char inserts #extmarks', function() + -- insertchar in edit.c (the ins_str branch) + set_extmark(ns, marks[1], 0, 3) + feed('0') + insert('abc') + screen:expect([[ + ab^c12345 | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + | + ]]) + local rv = curbufmeths.get_extmark_by_id(ns, marks[1]) + eq({0, 6}, rv) + -- check_undo_redo(ns, marks[1], 0, 2, 0, 5) + end) + + -- gravity right as definted in tk library + it('marks have gravity right #extmarks', function() + -- insertchar in edit.c (the ins_str branch) + set_extmark(ns, marks[1], 0, 2) + feed('03l') + insert("X") + check_undo_redo(ns, marks[1], 0, 2, 0, 2) + + -- check multibyte chars + feed('03l<esc>') + insert("~~") + check_undo_redo(ns, marks[1], 0, 2, 0, 2) + end) + + it('we can insert multibyte chars #extmarks', function() + -- insertchar in edit.c + feed('a<cr>12345<esc>') + set_extmark(ns, marks[1], 1, 2) + -- Insert a fullwidth (two col) tilde, NICE + feed('0i~<esc>') + check_undo_redo(ns, marks[1], 1, 2, 1, 3) + end) + + it('marks move with blockwise inserts #extmarks', function() + -- op_insert in ops.c + feed('a<cr>12345<esc>') + set_extmark(ns, marks[1], 1, 2) + feed('0<c-v>lkI9<esc>') + check_undo_redo(ns, marks[1], 1, 2, 1, 3) + end) + + it('marks move with line splits (using enter) #extmarks', function() + -- open_line in misc1.c + -- testing marks below are also moved + feed("yyP") + set_extmark(ns, marks[1], 0, 4) + set_extmark(ns, marks[2], 1, 4) + feed('1Gla<cr><esc>') + check_undo_redo(ns, marks[1], 0, 4, 1, 2) + check_undo_redo(ns, marks[2], 1, 4, 2, 4) + end) + + it('marks at last line move on insert new line #extmarks', function() + -- open_line in misc1.c + set_extmark(ns, marks[1], 0, 4) + feed('0i<cr><esc>') + check_undo_redo(ns, marks[1], 0, 4, 1, 4) + end) + + it('yet again marks move with line splits #extmarks', function() + -- the first test above wasn't catching all errors.. + feed("A67890<esc>") + set_extmark(ns, marks[1], 0, 4) + feed("04li<cr><esc>") + check_undo_redo(ns, marks[1], 0, 4, 1, 0) + end) + + it('and one last time line splits... #extmarks', function() + set_extmark(ns, marks[1], 0, 1) + set_extmark(ns, marks[2], 0, 2) + feed("02li<cr><esc>") + check_undo_redo(ns, marks[1], 0, 1, 0, 1) + check_undo_redo(ns, marks[2], 0, 2, 1, 0) + end) + + it('multiple marks move with mark splits #extmarks', function() + set_extmark(ns, marks[1], 0, 1) + set_extmark(ns, marks[2], 0, 3) + feed("0li<cr><esc>") + check_undo_redo(ns, marks[1], 0, 1, 1, 0) + check_undo_redo(ns, marks[2], 0, 3, 1, 2) + end) + + it('deleting on a mark works #extmarks', function() + -- op_delete in ops.c + set_extmark(ns, marks[1], 0, 2) + feed('02lx') + check_undo_redo(ns, marks[1], 0, 2, 0, 2) + end) + + it('marks move with char deletes #extmarks', function() + -- op_delete in ops.c + set_extmark(ns, marks[1], 0, 2) + feed('02dl') + check_undo_redo(ns, marks[1], 0, 2, 0, 0) + -- from the other side (nothing should happen) + feed('$x') + check_undo_redo(ns, marks[1], 0, 0, 0, 0) + end) + + it('marks move with char deletes over a range #extmarks', function() + -- op_delete in ops.c + set_extmark(ns, marks[1], 0, 2) + set_extmark(ns, marks[2], 0, 3) + feed('0l3dl<esc>') + check_undo_redo(ns, marks[1], 0, 2, 0, 1) + check_undo_redo(ns, marks[2], 0, 3, 0, 1) + -- delete 1, nothing should happend to our marks + feed('u') + feed('$x') + check_undo_redo(ns, marks[2], 0, 3, 0, 3) + end) + + it('deleting marks at end of line works #extmarks', function() + -- mark_extended.c/extmark_col_adjust_delete + set_extmark(ns, marks[1], 0, 4) + feed('$x') + check_undo_redo(ns, marks[1], 0, 4, 0, 4) + -- check the copy happened correctly on delete at eol + feed('$x') + check_undo_redo(ns, marks[1], 0, 4, 0, 3) + feed('u') + check_undo_redo(ns, marks[1], 0, 4, 0, 4) + end) + + it('marks move with blockwise deletes #extmarks', function() + -- op_delete in ops.c + feed('a<cr>12345<esc>') + set_extmark(ns, marks[1], 1, 4) + feed('h<c-v>hhkd') + check_undo_redo(ns, marks[1], 1, 4, 1, 1) + end) + + it('marks move with blockwise deletes over a range #extmarks', function() + -- op_delete in ops.c + feed('a<cr>12345<esc>') + set_extmark(ns, marks[1], 0, 1) + set_extmark(ns, marks[2], 0, 3) + set_extmark(ns, marks[3], 1, 2) + feed('0<c-v>k3lx') + check_undo_redo(ns, marks[1], 0, 1, 0, 0) + check_undo_redo(ns, marks[2], 0, 3, 0, 0) + check_undo_redo(ns, marks[3], 1, 2, 1, 0) + -- delete 1, nothing should happend to our marks + feed('u') + feed('$<c-v>jx') + check_undo_redo(ns, marks[2], 0, 3, 0, 3) + check_undo_redo(ns, marks[3], 1, 2, 1, 2) + end) + + it('works with char deletes over multilines #extmarks', function() + feed('a<cr>12345<cr>test-me<esc>') + set_extmark(ns, marks[1], 2, 5) + feed('gg') + feed('dv?-m?<cr>') + check_undo_redo(ns, marks[1], 2, 5, 0, 0) + end) + + it('marks outside of deleted range move with visual char deletes #extmarks', function() + -- op_delete in ops.c + set_extmark(ns, marks[1], 0, 3) + feed('0vx<esc>') + check_undo_redo(ns, marks[1], 0, 3, 0, 2) + + feed("u") + feed('0vlx<esc>') + check_undo_redo(ns, marks[1], 0, 3, 0, 1) + + feed("u") + feed('0v2lx<esc>') + check_undo_redo(ns, marks[1], 0, 3, 0, 0) + + -- from the other side (nothing should happen) + feed('$vx') + check_undo_redo(ns, marks[1], 0, 0, 0, 0) + end) + + it('marks outside of deleted range move with char deletes #extmarks', function() + -- op_delete in ops.c + set_extmark(ns, marks[1], 0, 3) + feed('0x<esc>') + check_undo_redo(ns, marks[1], 0, 3, 0, 2) + + feed("u") + feed('02x<esc>') + check_undo_redo(ns, marks[1], 0, 3, 0, 1) + + feed("u") + feed('0v3lx<esc>') + check_undo_redo(ns, marks[1], 0, 3, 0, 0) + + -- from the other side (nothing should happen) + feed("u") + feed('$vx') + check_undo_redo(ns, marks[1], 0, 3, 0, 3) + end) + + it('marks move with P(backward) paste #extmarks', function() + -- do_put in ops.c + feed('0iabc<esc>') + set_extmark(ns, marks[1], 0, 7) + feed('0veyP') + check_undo_redo(ns, marks[1], 0, 7, 0, 15) + end) + + it('marks move with p(forward) paste #extmarks', function() + -- do_put in ops.c + feed('0iabc<esc>') + set_extmark(ns, marks[1], 0, 7) + feed('0veyp') + check_undo_redo(ns, marks[1], 0, 7, 0, 14) + end) + + it('marks move with blockwise P(backward) paste #extmarks', function() + -- do_put in ops.c + feed('a<cr>12345<esc>') + set_extmark(ns, marks[1], 1, 4) + feed('<c-v>hhkyP<esc>') + check_undo_redo(ns, marks[1], 1, 4, 1, 7) + end) + + it('marks move with blockwise p(forward) paste #extmarks', function() + -- do_put in ops.c + feed('a<cr>12345<esc>') + set_extmark(ns, marks[1], 1, 4) + feed('<c-v>hhkyp<esc>') + check_undo_redo(ns, marks[1], 1, 4, 1, 6) + end) + + it('replace works #extmarks', function() + set_extmark(ns, marks[1], 0, 2) + feed('0r2') + check_undo_redo(ns, marks[1], 0, 2, 0, 2) + end) + + it('blockwise replace works #extmarks', function() + feed('a<cr>12345<esc>') + set_extmark(ns, marks[1], 0, 2) + feed('0<c-v>llkr1<esc>') + check_undo_redo(ns, marks[1], 0, 2, 0, 2) + end) + + it('shift line #extmarks', function() + -- shift_line in ops.c + feed(':set shiftwidth=4<cr><esc>') + set_extmark(ns, marks[1], 0, 2) + feed('0>>') + check_undo_redo(ns, marks[1], 0, 2, 0, 6) + + feed('>>') + check_undo_redo(ns, marks[1], 0, 6, 0, 10) + + feed('<LT><LT>') -- have to escape, same as << + check_undo_redo(ns, marks[1], 0, 10, 0, 6) + end) + + it('blockwise shift #extmarks', function() + -- shift_block in ops.c + feed(':set shiftwidth=4<cr><esc>') + feed('a<cr>12345<esc>') + set_extmark(ns, marks[1], 1, 2) + feed('0<c-v>k>') + check_undo_redo(ns, marks[1], 1, 2, 1, 6) + feed('<c-v>j>') + check_undo_redo(ns, marks[1], 1, 6, 1, 10) + + feed('<c-v>j<LT>') + check_undo_redo(ns, marks[1], 1, 10, 1, 6) + end) + + it('tab works with expandtab #extmarks', function() + -- ins_tab in edit.c + feed(':set expandtab<cr><esc>') + feed(':set shiftwidth=2<cr><esc>') + set_extmark(ns, marks[1], 0, 2) + feed('0i<tab><tab><esc>') + check_undo_redo(ns, marks[1], 0, 2, 0, 6) + end) + + it('tabs work #extmarks', function() + -- ins_tab in edit.c + feed(':set noexpandtab<cr><esc>') + feed(':set shiftwidth=2<cr><esc>') + feed(':set softtabstop=2<cr><esc>') + feed(':set tabstop=8<cr><esc>') + set_extmark(ns, marks[1], 0, 2) + feed('0i<tab><esc>') + check_undo_redo(ns, marks[1], 0, 2, 0, 4) + feed('0iX<tab><esc>') + check_undo_redo(ns, marks[1], 0, 4, 0, 6) + end) + + it('marks move when using :move #extmarks', function() + set_extmark(ns, marks[1], 0, 0) + feed('A<cr>2<esc>:1move 2<cr><esc>') + check_undo_redo(ns, marks[1], 0, 0, 1, 0) + -- test codepath when moving lines up + feed(':2move 0<cr><esc>') + check_undo_redo(ns, marks[1], 1, 0, 0, 0) + end) + + it('marks move when using :move part 2 #extmarks', function() + -- make sure we didn't get lucky with the math... + feed('A<cr>2<cr>3<cr>4<cr>5<cr>6<esc>') + set_extmark(ns, marks[1], 1, 0) + feed(':2,3move 5<cr><esc>') + check_undo_redo(ns, marks[1], 1, 0, 3, 0) + -- test codepath when moving lines up + feed(':4,5move 1<cr><esc>') + check_undo_redo(ns, marks[1], 3, 0, 1, 0) + end) + + it('undo and redo of set and unset marks #extmarks', function() + -- Force a new undo head + feed('o<esc>') + set_extmark(ns, marks[1], 0, 1) + feed('o<esc>') + set_extmark(ns, marks[2], 0, -1) + set_extmark(ns, marks[3], 0, -1) + + feed("u") + local rv = get_extmarks(ns, {0, 0}, {-1, -1}) + eq(1, table.getn(rv)) + + feed("<c-r>") + rv = get_extmarks(ns, {0, 0}, {-1, -1}) + eq(3, table.getn(rv)) + + -- Test updates + feed('o<esc>') + set_extmark(ns, marks[1], positions[1][1], positions[1][2]) + rv = get_extmarks(ns, marks[1], marks[1], {amount=1}) + eq(1, table.getn(rv)) + feed("u") + feed("<c-r>") + check_undo_redo(ns, marks[1], 0, 1, positions[1][1], positions[1][2]) + + -- Test unset + feed('o<esc>') + curbufmeths.del_extmark(ns, marks[3]) + feed("u") + rv = get_extmarks(ns, {0, 0}, {-1, -1}) + eq(3, table.getn(rv)) + feed("<c-r>") + rv = get_extmarks(ns, {0, 0}, {-1, -1}) + eq(2, table.getn(rv)) + end) + + it('undo and redo of marks deleted during edits #extmarks', function() + -- test extmark_adjust + feed('A<cr>12345<esc>') + set_extmark(ns, marks[1], 1, 2) + feed('dd') + check_undo_redo(ns, marks[1], 1, 2, 1, 0) + end) + + it('namespaces work properly #extmarks', function() + local rv = set_extmark(ns, marks[1], positions[1][1], positions[1][2]) + eq(1, rv) + rv = set_extmark(ns2, marks[1], positions[1][1], positions[1][2]) + eq(1, rv) + rv = get_extmarks(ns, {0, 0}, {-1, -1}) + eq(1, table.getn(rv)) + rv = get_extmarks(ns2, {0, 0}, {-1, -1}) + eq(1, table.getn(rv)) + + -- Set more marks for testing the ranges + set_extmark(ns, marks[2], positions[2][1], positions[2][2]) + set_extmark(ns, marks[3], positions[3][1], positions[3][2]) + set_extmark(ns2, marks[2], positions[2][1], positions[2][2]) + set_extmark(ns2, marks[3], positions[3][1], positions[3][2]) + + -- get_next (amount set) + rv = get_extmarks(ns, {0, 0}, positions[2], {amount=1}) + eq(1, table.getn(rv)) + rv = get_extmarks(ns2, {0, 0}, positions[2], {amount=1}) + eq(1, table.getn(rv)) + -- get_prev (amount set) + rv = get_extmarks(ns, positions[1], {0, 0}, {amount=1}) + eq(1, table.getn(rv)) + rv = get_extmarks(ns2, positions[1], {0, 0}, {amount=1}) + eq(1, table.getn(rv)) + + -- get_next (amount not set) + rv = get_extmarks(ns, positions[1], positions[2]) + eq(2, table.getn(rv)) + rv = get_extmarks(ns2, positions[1], positions[2]) + eq(2, table.getn(rv)) + -- get_prev (amount not set) + rv = get_extmarks(ns, positions[2], positions[1]) + eq(2, table.getn(rv)) + rv = get_extmarks(ns2, positions[2], positions[1]) + eq(2, table.getn(rv)) + + curbufmeths.del_extmark(ns, marks[1]) + rv = get_extmarks(ns, {0, 0}, {-1, -1}) + eq(2, table.getn(rv)) + curbufmeths.del_extmark(ns2, marks[1]) + rv = get_extmarks(ns2, {0, 0}, {-1, -1}) + eq(2, table.getn(rv)) + end) + + it('mark set can create unique identifiers #extmarks', function() + -- create mark with id 1 + eq(1, set_extmark(ns, 1, positions[1][1], positions[1][2])) + -- ask for unique id, it should be the next one, i e 2 + eq(2, set_extmark(ns, 0, positions[1][1], positions[1][2])) + eq(3, set_extmark(ns, 3, positions[2][1], positions[2][2])) + eq(4, set_extmark(ns, 0, positions[1][1], positions[1][2])) + + -- mixing manual and allocated id:s are not recommened, but it should + -- do something reasonable + eq(6, set_extmark(ns, 6, positions[2][1], positions[2][2])) + eq(7, set_extmark(ns, 0, positions[1][1], positions[1][2])) + eq(8, set_extmark(ns, 0, positions[1][1], positions[1][2])) + end) + + it('auto indenting with enter works #extmarks', function() + -- op_reindent in ops.c + feed(':set cindent<cr><esc>') + feed(':set autoindent<cr><esc>') + feed(':set shiftwidth=2<cr><esc>') + feed("0iint <esc>A {1M1<esc>b<esc>") + -- Set the mark on the M, should move.. + set_extmark(ns, marks[1], 0, 12) + -- Set the mark before the cursor, should stay there + set_extmark(ns, marks[2], 0, 10) + feed("i<cr><esc>") + local rv = curbufmeths.get_extmark_by_id(ns, marks[1]) + eq({1, 3}, rv) + rv = curbufmeths.get_extmark_by_id(ns, marks[2]) + eq({0, 10}, rv) + check_undo_redo(ns, marks[1], 0, 12, 1, 3) + end) + + it('auto indenting entire line works #extmarks', function() + feed(':set cindent<cr><esc>') + feed(':set autoindent<cr><esc>') + feed(':set shiftwidth=2<cr><esc>') + -- <c-f> will force an indent of 2 + feed("0iint <esc>A {<cr><esc>0i1M1<esc>") + set_extmark(ns, marks[1], 1, 1) + feed("0i<c-f><esc>") + local rv = curbufmeths.get_extmark_by_id(ns, marks[1]) + eq({1, 3}, rv) + check_undo_redo(ns, marks[1], 1, 1, 1, 3) + -- now check when cursor at eol + feed("uA<c-f><esc>") + rv = curbufmeths.get_extmark_by_id(ns, marks[1]) + eq({1, 3}, rv) + end) + + it('removing auto indenting with <C-D> works #extmarks', function() + feed(':set cindent<cr><esc>') + feed(':set autoindent<cr><esc>') + feed(':set shiftwidth=2<cr><esc>') + feed("0i<tab><esc>") + set_extmark(ns, marks[1], 0, 3) + feed("bi<c-d><esc>") + local rv = curbufmeths.get_extmark_by_id(ns, marks[1]) + eq({0, 1}, rv) + check_undo_redo(ns, marks[1], 0, 3, 0, 1) + -- check when cursor at eol + feed("uA<c-d><esc>") + rv = curbufmeths.get_extmark_by_id(ns, marks[1]) + eq({0, 1}, rv) + end) + + it('indenting multiple lines with = works #extmarks', function() + feed(':set cindent<cr><esc>') + feed(':set autoindent<cr><esc>') + feed(':set shiftwidth=2<cr><esc>') + feed("0iint <esc>A {<cr><bs>1M1<cr><bs>2M2<esc>") + set_extmark(ns, marks[1], 1, 1) + set_extmark(ns, marks[2], 2, 1) + feed('=gg') + check_undo_redo(ns, marks[1], 1, 1, 1, 3) + check_undo_redo(ns, marks[2], 2, 1, 2, 5) + end) + + it('substitutes by deleting inside the replace matches #extmarks_sub', function() + -- do_sub in ex_cmds.c + set_extmark(ns, marks[1], 0, 2) + set_extmark(ns, marks[2], 0, 3) + feed(':s/34/xx<cr>') + check_undo_redo(ns, marks[1], 0, 2, 0, 4) + check_undo_redo(ns, marks[2], 0, 3, 0, 4) + end) + + it('substitutes when insert text > deleted #extmarks_sub', function() + -- do_sub in ex_cmds.c + set_extmark(ns, marks[1], 0, 2) + set_extmark(ns, marks[2], 0, 3) + feed(':s/34/xxx<cr>') + check_undo_redo(ns, marks[1], 0, 2, 0, 5) + check_undo_redo(ns, marks[2], 0, 3, 0, 5) + end) + + it('substitutes when marks around eol #extmarks_sub', function() + -- do_sub in ex_cmds.c + set_extmark(ns, marks[1], 0, 4) + set_extmark(ns, marks[2], 0, 5) + feed(':s/5/xxx<cr>') + check_undo_redo(ns, marks[1], 0, 4, 0, 7) + check_undo_redo(ns, marks[2], 0, 5, 0, 7) + end) + + it('substitutes over range insert text > deleted #extmarks_sub', function() + -- do_sub in ex_cmds.c + feed('A<cr>x34xx<esc>') + feed('A<cr>xxx34<esc>') + set_extmark(ns, marks[1], 0, 2) + set_extmark(ns, marks[2], 1, 1) + set_extmark(ns, marks[3], 2, 4) + feed(':1,3s/34/xxx<cr><esc>') + check_undo_redo(ns, marks[1], 0, 2, 0, 5) + check_undo_redo(ns, marks[2], 1, 1, 1, 4) + check_undo_redo(ns, marks[3], 2, 4, 2, 6) + end) + + it('substitutes multiple matches in a line #extmarks_sub', function() + -- do_sub in ex_cmds.c + feed('ddi3x3x3<esc>') + set_extmark(ns, marks[1], 0, 0) + set_extmark(ns, marks[2], 0, 2) + set_extmark(ns, marks[3], 0, 4) + feed(':s/3/yy/g<cr><esc>') + check_undo_redo(ns, marks[1], 0, 0, 0, 2) + check_undo_redo(ns, marks[2], 0, 2, 0, 5) + check_undo_redo(ns, marks[3], 0, 4, 0, 8) + end) + + it('substitions over multiple lines with newline in pattern #extmarks_sub', function() + feed('A<cr>67890<cr>xx<esc>') + set_extmark(ns, marks[1], 0, 3) + set_extmark(ns, marks[2], 0, 4) + set_extmark(ns, marks[3], 1, 0) + set_extmark(ns, marks[4], 1, 5) + set_extmark(ns, marks[5], 2, 0) + feed([[:1,2s:5\n:5 <cr>]]) + check_undo_redo(ns, marks[1], 0, 3, 0, 3) + check_undo_redo(ns, marks[2], 0, 4, 0, 6) + check_undo_redo(ns, marks[3], 1, 0, 0, 6) + check_undo_redo(ns, marks[4], 1, 5, 0, 11) + check_undo_redo(ns, marks[5], 2, 0, 1, 0) + end) + + it('inserting #extmarks_sub', function() + feed('A<cr>67890<cr>xx<esc>') + set_extmark(ns, marks[1], 0, 3) + set_extmark(ns, marks[2], 0, 4) + set_extmark(ns, marks[3], 1, 0) + set_extmark(ns, marks[4], 1, 5) + set_extmark(ns, marks[5], 2, 0) + set_extmark(ns, marks[6], 1, 2) + feed([[:1,2s:5\n67:X<cr>]]) + check_undo_redo(ns, marks[1], 0, 3, 0, 3) + check_undo_redo(ns, marks[2], 0, 4, 0, 5) + check_undo_redo(ns, marks[3], 1, 0, 0, 5) + check_undo_redo(ns, marks[4], 1, 5, 0, 8) + check_undo_redo(ns, marks[5], 2, 0, 1, 0) + check_undo_redo(ns, marks[6], 1, 2, 0, 5) + end) + + it('substitions with multiple newlines in pattern #extmarks_sub', function() + feed('A<cr>67890<cr>xx<esc>') + set_extmark(ns, marks[1], 0, 4) + set_extmark(ns, marks[2], 0, 5) + set_extmark(ns, marks[3], 1, 0) + set_extmark(ns, marks[4], 1, 5) + set_extmark(ns, marks[5], 2, 0) + feed([[:1,2s:\n.*\n:X<cr>]]) + check_undo_redo(ns, marks[1], 0, 4, 0, 4) + check_undo_redo(ns, marks[2], 0, 5, 0, 6) + check_undo_redo(ns, marks[3], 1, 0, 0, 6) + check_undo_redo(ns, marks[4], 1, 5, 0, 6) + check_undo_redo(ns, marks[5], 2, 0, 0, 6) + end) + + it('substitions over multiple lines with replace in substition #extmarks_sub', function() + feed('A<cr>67890<cr>xx<esc>') + set_extmark(ns, marks[1], 0, 1) + set_extmark(ns, marks[2], 0, 2) + set_extmark(ns, marks[3], 0, 4) + set_extmark(ns, marks[4], 1, 0) + set_extmark(ns, marks[5], 2, 0) + feed([[:1,2s:3:\r<cr>]]) + check_undo_redo(ns, marks[1], 0, 1, 0, 1) + check_undo_redo(ns, marks[2], 0, 2, 1, 0) + check_undo_redo(ns, marks[3], 0, 4, 1, 1) + check_undo_redo(ns, marks[4], 1, 0, 2, 0) + check_undo_redo(ns, marks[5], 2, 0, 3, 0) + feed('u') + feed([[:1,2s:3:\rxx<cr>]]) + eq({1, 3}, curbufmeths.get_extmark_by_id(ns, marks[3])) + end) + + it('substitions over multiple lines with replace in substition #extmarks_sub', function() + feed('A<cr>x3<cr>xx<esc>') + set_extmark(ns, marks[1], 1, 0) + set_extmark(ns, marks[2], 1, 1) + set_extmark(ns, marks[3], 1, 2) + feed([[:2,2s:3:\r<cr>]]) + check_undo_redo(ns, marks[1], 1, 0, 1, 0) + check_undo_redo(ns, marks[2], 1, 1, 2, 0) + check_undo_redo(ns, marks[3], 1, 2, 2, 0) + end) + + it('substitions over multiple lines with replace in substition #extmarks_sub', function() + feed('A<cr>x3<cr>xx<esc>') + set_extmark(ns, marks[1], 0, 1) + set_extmark(ns, marks[2], 0, 2) + set_extmark(ns, marks[3], 0, 4) + set_extmark(ns, marks[4], 1, 1) + set_extmark(ns, marks[5], 2, 0) + feed([[:1,2s:3:\r<cr>]]) + check_undo_redo(ns, marks[1], 0, 1, 0, 1) + check_undo_redo(ns, marks[2], 0, 2, 1, 0) + check_undo_redo(ns, marks[3], 0, 4, 1, 1) + check_undo_redo(ns, marks[4], 1, 1, 3, 0) + check_undo_redo(ns, marks[5], 2, 0, 4, 0) + feed('u') + feed([[:1,2s:3:\rxx<cr>]]) + check_undo_redo(ns, marks[3], 0, 4, 1, 3) + end) + + it('substitions with newline in match and sub, delta is 0 #extmarks_sub', function() + feed('A<cr>67890<cr>xx<esc>') + set_extmark(ns, marks[1], 0, 3) + set_extmark(ns, marks[2], 0, 4) + set_extmark(ns, marks[3], 0, 5) + set_extmark(ns, marks[4], 1, 0) + set_extmark(ns, marks[5], 1, 5) + set_extmark(ns, marks[6], 2, 0) + feed([[:1,1s:5\n:\r<cr>]]) + check_undo_redo(ns, marks[1], 0, 3, 0, 3) + check_undo_redo(ns, marks[2], 0, 4, 1, 0) + check_undo_redo(ns, marks[3], 0, 5, 1, 0) + check_undo_redo(ns, marks[4], 1, 0, 1, 0) + check_undo_redo(ns, marks[5], 1, 5, 1, 5) + check_undo_redo(ns, marks[6], 2, 0, 2, 0) + end) + + it('substitions with newline in match and sub, delta > 0 #extmarks_sub', function() + feed('A<cr>67890<cr>xx<esc>') + set_extmark(ns, marks[1], 0, 3) + set_extmark(ns, marks[2], 0, 4) + set_extmark(ns, marks[3], 0, 5) + set_extmark(ns, marks[4], 1, 0) + set_extmark(ns, marks[5], 1, 5) + set_extmark(ns, marks[6], 2, 0) + feed([[:1,1s:5\n:\r\r<cr>]]) + check_undo_redo(ns, marks[1], 0, 3, 0, 3) + check_undo_redo(ns, marks[2], 0, 4, 2, 0) + check_undo_redo(ns, marks[3], 0, 5, 2, 0) + check_undo_redo(ns, marks[4], 1, 0, 2, 0) + check_undo_redo(ns, marks[5], 1, 5, 2, 5) + check_undo_redo(ns, marks[6], 2, 0, 3, 0) + end) + + it('substitions with newline in match and sub, delta < 0 #extmarks_sub', function() + feed('A<cr>67890<cr>xx<cr>xx<esc>') + set_extmark(ns, marks[1], 0, 3) + set_extmark(ns, marks[2], 0, 4) + set_extmark(ns, marks[3], 0, 5) + set_extmark(ns, marks[4], 1, 0) + set_extmark(ns, marks[5], 1, 5) + set_extmark(ns, marks[6], 2, 1) + set_extmark(ns, marks[7], 3, 0) + feed([[:1,2s:5\n.*\n:\r<cr>]]) + check_undo_redo(ns, marks[1], 0, 3, 0, 3) + check_undo_redo(ns, marks[2], 0, 4, 1, 0) + check_undo_redo(ns, marks[3], 0, 5, 1, 0) + check_undo_redo(ns, marks[4], 1, 0, 1, 0) + check_undo_redo(ns, marks[5], 1, 5, 1, 0) + check_undo_redo(ns, marks[6], 2, 1, 1, 1) + check_undo_redo(ns, marks[7], 3, 0, 2, 0) + end) + + it('substitions with backrefs, newline inserted into sub #extmarks_sub', function() + feed('A<cr>67890<cr>xx<cr>xx<esc>') + set_extmark(ns, marks[1], 0, 3) + set_extmark(ns, marks[2], 0, 4) + set_extmark(ns, marks[3], 0, 5) + set_extmark(ns, marks[4], 1, 0) + set_extmark(ns, marks[5], 1, 5) + set_extmark(ns, marks[6], 2, 0) + feed([[:1,1s:5\(\n\):\0\1<cr>]]) + check_undo_redo(ns, marks[1], 0, 3, 0, 3) + check_undo_redo(ns, marks[2], 0, 4, 2, 0) + check_undo_redo(ns, marks[3], 0, 5, 2, 0) + check_undo_redo(ns, marks[4], 1, 0, 2, 0) + check_undo_redo(ns, marks[5], 1, 5, 2, 5) + check_undo_redo(ns, marks[6], 2, 0, 3, 0) + end) + + it('substitions a ^ #extmarks_sub', function() + set_extmark(ns, marks[1], 0, 0) + set_extmark(ns, marks[2], 0, 1) + feed([[:s:^:x<cr>]]) + check_undo_redo(ns, marks[1], 0, 0, 0, 1) + check_undo_redo(ns, marks[2], 0, 1, 0, 2) + end) + + it('using <c-a> without increase in order of magnitude #extmarks_inc_dec', function() + -- do_addsub in ops.c + feed('ddiabc998xxx<esc>Tc') + set_extmark(ns, marks[1], 0, 2) + set_extmark(ns, marks[2], 0, 3) + set_extmark(ns, marks[3], 0, 5) + set_extmark(ns, marks[4], 0, 6) + set_extmark(ns, marks[5], 0, 7) + feed('<c-a>') + check_undo_redo(ns, marks[1], 0, 2, 0, 2) + check_undo_redo(ns, marks[2], 0, 3, 0, 6) + check_undo_redo(ns, marks[3], 0, 5, 0, 6) + check_undo_redo(ns, marks[4], 0, 6, 0, 6) + check_undo_redo(ns, marks[5], 0, 7, 0, 7) + end) + + it('using <c-a> when increase in order of magnitude #extmarks_inc_dec', function() + -- do_addsub in ops.c + feed('ddiabc999xxx<esc>Tc') + set_extmark(ns, marks[1], 0, 2) + set_extmark(ns, marks[2], 0, 3) + set_extmark(ns, marks[3], 0, 5) + set_extmark(ns, marks[4], 0, 6) + set_extmark(ns, marks[5], 0, 7) + feed('<c-a>') + check_undo_redo(ns, marks[1], 0, 2, 0, 2) + check_undo_redo(ns, marks[2], 0, 3, 0, 7) + check_undo_redo(ns, marks[3], 0, 5, 0, 7) + check_undo_redo(ns, marks[4], 0, 6, 0, 7) + check_undo_redo(ns, marks[5], 0, 7, 0, 8) + end) + + it('using <c-a> when negative and without decrease in order of magnitude #extmarks_inc_dec', function() + feed('ddiabc-999xxx<esc>T-') + set_extmark(ns, marks[1], 0, 2) + set_extmark(ns, marks[2], 0, 3) + set_extmark(ns, marks[3], 0, 6) + set_extmark(ns, marks[4], 0, 7) + set_extmark(ns, marks[5], 0, 8) + feed('<c-a>') + check_undo_redo(ns, marks[1], 0, 2, 0, 2) + check_undo_redo(ns, marks[2], 0, 3, 0, 7) + check_undo_redo(ns, marks[3], 0, 6, 0, 7) + check_undo_redo(ns, marks[4], 0, 7, 0, 7) + check_undo_redo(ns, marks[5], 0, 8, 0, 8) + end) + + it('using <c-a> when negative and decrease in order of magnitude #extmarks_inc_dec', function() + feed('ddiabc-1000xxx<esc>T-') + set_extmark(ns, marks[1], 0, 2) + set_extmark(ns, marks[2], 0, 3) + set_extmark(ns, marks[3], 0, 7) + set_extmark(ns, marks[4], 0, 8) + set_extmark(ns, marks[5], 0, 9) + feed('<c-a>') + check_undo_redo(ns, marks[1], 0, 2, 0, 2) + check_undo_redo(ns, marks[2], 0, 3, 0, 7) + check_undo_redo(ns, marks[3], 0, 7, 0, 7) + check_undo_redo(ns, marks[4], 0, 8, 0, 7) + check_undo_redo(ns, marks[5], 0, 9, 0, 8) + end) + + it('using <c-x> without decrease in order of magnitude #extmarks_inc_dec', function() + -- do_addsub in ops.c + feed('ddiabc999xxx<esc>Tc') + set_extmark(ns, marks[1], 0, 2) + set_extmark(ns, marks[2], 0, 3) + set_extmark(ns, marks[3], 0, 5) + set_extmark(ns, marks[4], 0, 6) + set_extmark(ns, marks[5], 0, 7) + feed('<c-x>') + check_undo_redo(ns, marks[1], 0, 2, 0, 2) + check_undo_redo(ns, marks[2], 0, 3, 0, 6) + check_undo_redo(ns, marks[3], 0, 5, 0, 6) + check_undo_redo(ns, marks[4], 0, 6, 0, 6) + check_undo_redo(ns, marks[5], 0, 7, 0, 7) + end) + + it('using <c-x> when decrease in order of magnitude #extmarks_inc_dec', function() + -- do_addsub in ops.c + feed('ddiabc1000xxx<esc>Tc') + set_extmark(ns, marks[1], 0, 2) + set_extmark(ns, marks[2], 0, 3) + set_extmark(ns, marks[3], 0, 6) + set_extmark(ns, marks[4], 0, 7) + set_extmark(ns, marks[5], 0, 8) + feed('<c-x>') + check_undo_redo(ns, marks[1], 0, 2, 0, 2) + check_undo_redo(ns, marks[2], 0, 3, 0, 6) + check_undo_redo(ns, marks[3], 0, 6, 0, 6) + check_undo_redo(ns, marks[4], 0, 7, 0, 6) + check_undo_redo(ns, marks[5], 0, 8, 0, 7) + end) + + it('using <c-x> when negative and without increase in order of magnitude #extmarks_inc_dec', function() + feed('ddiabc-998xxx<esc>T-') + set_extmark(ns, marks[1], 0, 2) + set_extmark(ns, marks[2], 0, 3) + set_extmark(ns, marks[3], 0, 6) + set_extmark(ns, marks[4], 0, 7) + set_extmark(ns, marks[5], 0, 8) + feed('<c-x>') + check_undo_redo(ns, marks[1], 0, 2, 0, 2) + check_undo_redo(ns, marks[2], 0, 3, 0, 7) + check_undo_redo(ns, marks[3], 0, 6, 0, 7) + check_undo_redo(ns, marks[4], 0, 7, 0, 7) + check_undo_redo(ns, marks[5], 0, 8, 0, 8) + end) + + it('using <c-x> when negative and increase in order of magnitude #extmarks_inc_dec', function() + feed('ddiabc-999xxx<esc>T-') + set_extmark(ns, marks[1], 0, 2) + set_extmark(ns, marks[2], 0, 3) + set_extmark(ns, marks[3], 0, 6) + set_extmark(ns, marks[4], 0, 7) + set_extmark(ns, marks[5], 0, 8) + feed('<c-x>') + check_undo_redo(ns, marks[1], 0, 2, 0, 2) + check_undo_redo(ns, marks[2], 0, 3, 0, 8) + check_undo_redo(ns, marks[3], 0, 6, 0, 8) + check_undo_redo(ns, marks[4], 0, 7, 0, 8) + check_undo_redo(ns, marks[5], 0, 8, 0, 9) + end) + + it('throws consistent error codes', function() + local ns_invalid = ns2 + 1 + eq("Invalid ns_id", pcall_err(set_extmark, ns_invalid, marks[1], positions[1][1], positions[1][2])) + eq("Invalid ns_id", pcall_err(curbufmeths.del_extmark, ns_invalid, marks[1])) + eq("Invalid ns_id", pcall_err(get_extmarks, ns_invalid, positions[1], positions[2])) + eq("Invalid ns_id", pcall_err(curbufmeths.get_extmark_by_id, ns_invalid, marks[1])) + end) + + it('when col = line-length, set the mark on eol #extmarks', function() + set_extmark(ns, marks[1], 0, -1) + local rv = curbufmeths.get_extmark_by_id(ns, marks[1]) + eq({0, init_text:len()}, rv) + -- Test another + set_extmark(ns, marks[1], 0, -1) + rv = curbufmeths.get_extmark_by_id(ns, marks[1]) + eq({0, init_text:len()}, rv) + end) + + it('when col = line-length, set the mark on eol #extmarks', function() + local invalid_col = init_text:len() + 1 + eq("col value outside range", pcall_err(set_extmark, ns, marks[1], 0, invalid_col)) + end) + + it('when line > line_count, throw error #extmarks', function() + local invalid_col = init_text:len() + 1 + local invalid_lnum = 3 + eq('line value outside range', pcall_err(set_extmark, ns, marks[1], invalid_lnum, invalid_col)) + eq({}, curbufmeths.get_extmark_by_id(ns, marks[1])) + end) + + it('bug from check_col in extmark_set #extmarks_sub', function() + -- This bug was caused by extmark_set always using + -- check_col. check_col always uses the current buffer. + -- This wasn't working during undo so we now use + -- check_col and check_lnum only when they are required. + feed('A<cr>67890<cr>xx<esc>') + feed('A<cr>12345<cr>67890<cr>xx<esc>') + set_extmark(ns, marks[1], 3, 4) + feed([[:1,5s:5\n:5 <cr>]]) + check_undo_redo(ns, marks[1], 3, 4, 2, 6) + end) + + it('in read-only buffer', function() + command("view! runtime/doc/help.txt") + eq(true, curbufmeths.get_option('ro')) + local id = set_extmark(ns, 0, 0, 2) + eq({{id, 0, 2}}, get_extmarks(ns,0, -1)) + end) +end) + +describe('Extmarks buffer api with many marks', function() + local ns1 + local ns2 + local ns_marks = {} + before_each(function() + clear() + ns1 = request('nvim_create_namespace', "ns1") + ns2 = request('nvim_create_namespace', "ns2") + ns_marks = {[ns1]={}, [ns2]={}} + local lines = {} + for i = 1,30 do + lines[#lines+1] = string.rep("x ",i) + end + curbufmeths.set_lines(0, -1, true, lines) + local ns = ns1 + local q = 0 + for i = 0,29 do + for j = 0,i do + local id = set_extmark(ns,0, i,j) + eq(nil, ns_marks[ns][id]) + ok(id > 0) + ns_marks[ns][id] = {i,j} + ns = ns1+ns2-ns + q = q + 1 + end + end + eq(233, #ns_marks[ns1]) + eq(232, #ns_marks[ns2]) + + end) + + local function get_marks(ns) + local mark_list = get_extmarks(ns, 0, -1) + local marks = {} + for _, mark in ipairs(mark_list) do + local id, row, col = unpack(mark) + eq(nil, marks[id], "duplicate mark") + marks[id] = {row,col} + end + return marks + end + + it("can get marks #extmarks", function() + eq(ns_marks[ns1], get_marks(ns1)) + eq(ns_marks[ns2], get_marks(ns2)) + end) + + it("can clear all marks in ns #extmarks", function() + curbufmeths.clear_namespace(ns1, 0, -1) + eq({}, get_marks(ns1)) + eq(ns_marks[ns2], get_marks(ns2)) + curbufmeths.clear_namespace(ns2, 0, -1) + eq({}, get_marks(ns1)) + eq({}, get_marks(ns2)) + end) + + it("can clear line range #extmarks", function() + curbufmeths.clear_namespace(ns1, 10, 20) + for id, mark in pairs(ns_marks[ns1]) do + if 10 <= mark[1] and mark[1] < 20 then + ns_marks[ns1][id] = nil + end + end + eq(ns_marks[ns1], get_marks(ns1)) + eq(ns_marks[ns2], get_marks(ns2)) + end) + + it("can delete line #extmarks", function() + feed('10Gdd') + for _, marks in pairs(ns_marks) do + for id, mark in pairs(marks) do + if mark[1] == 9 then + marks[id] = {9,0} + elseif mark[1] >= 10 then + mark[1] = mark[1] - 1 + end + end + end + eq(ns_marks[ns1], get_marks(ns1)) + eq(ns_marks[ns2], get_marks(ns2)) + end) + + it("can delete lines #extmarks", function() + feed('10G10dd') + for _, marks in pairs(ns_marks) do + for id, mark in pairs(marks) do + if 9 <= mark[1] and mark[1] < 19 then + marks[id] = {9,0} + elseif mark[1] >= 19 then + mark[1] = mark[1] - 10 + end + end + end + eq(ns_marks[ns1], get_marks(ns1)) + eq(ns_marks[ns2], get_marks(ns2)) + end) + + it("can wipe buffer #extmarks", function() + command('bwipe!') + eq({}, get_marks(ns1)) + eq({}, get_marks(ns2)) + end) +end) diff --git a/test/functional/core/fileio_spec.lua b/test/functional/core/fileio_spec.lua index e6bce85b8a..f4c476560d 100644 --- a/test/functional/core/fileio_spec.lua +++ b/test/functional/core/fileio_spec.lua @@ -9,9 +9,12 @@ local nvim_prog = helpers.nvim_prog local request = helpers.request local retry = helpers.retry local rmdir = helpers.rmdir +local mkdir = helpers.mkdir local sleep = helpers.sleep local read_file = helpers.read_file local trim = helpers.trim +local currentdir = helpers.funcs.getcwd +local iswin = helpers.iswin describe('fileio', function() before_each(function() @@ -24,6 +27,7 @@ describe('fileio', function() os.remove('Xtest_startup_file2') os.remove('Xtest_тест.md') rmdir('Xtest_startup_swapdir') + rmdir('Xtest_backupdir') end) it('fsync() codepaths #8304', function() @@ -88,6 +92,27 @@ describe('fileio', function() eq('foo', bar_contents); end) + it('backup with full path #11214', function() + clear() + mkdir('Xtest_backupdir') + command('set backup') + command('set backupdir=Xtest_backupdir//') + command('write Xtest_startup_file1') + feed('ifoo<esc>') + command('write') + feed('Abar<esc>') + command('write') + + -- Backup filename = fullpath, separators replaced with "%". + local backup_file_name = string.gsub(currentdir()..'/Xtest_startup_file1', + iswin() and '[:/\\]' or '/', '%%') .. '~' + local foo_contents = trim(read_file('Xtest_backupdir/'..backup_file_name)) + local foobar_contents = trim(read_file('Xtest_startup_file1')) + + eq('foobar', foobar_contents); + eq('foo', foo_contents); + end) + it('readfile() on multibyte filename #10586', function() clear() local text = { diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua index b793e531c9..37a9f0b836 100644 --- a/test/functional/core/main_spec.lua +++ b/test/functional/core/main_spec.lua @@ -67,7 +67,7 @@ describe('Command-line option', function() | | ]], { - [1] = {foreground = 4210943}, + [1] = {foreground = tonumber('0x4040ff'), fg_indexed=true}, [2] = {bold = true, reverse = true} }) feed('i:cq<CR>') diff --git a/test/functional/fixtures/lsp-test-rpc-server.lua b/test/functional/fixtures/lsp-test-rpc-server.lua new file mode 100644 index 0000000000..971e61b072 --- /dev/null +++ b/test/functional/fixtures/lsp-test-rpc-server.lua @@ -0,0 +1,424 @@ +local protocol = require 'vim.lsp.protocol' + +-- Internal utility methods. + +-- TODO replace with a better implementation. +local function json_encode(data) + local status, result = pcall(vim.fn.json_encode, data) + if status then + return result + else + return nil, result + end +end +local function json_decode(data) + local status, result = pcall(vim.fn.json_decode, data) + if status then + return result + else + return nil, result + end +end + +local function message_parts(sep, ...) + local parts = {} + for i = 1, select("#", ...) do + local arg = select(i, ...) + if arg ~= nil then + table.insert(parts, arg) + end + end + return table.concat(parts, sep) +end + +-- Assert utility methods + +local function assert_eq(a, b, ...) + if not vim.deep_equal(a, b) then + error(message_parts(": ", + ..., "assert_eq failed", + string.format("left == %q, right == %q", vim.inspect(a), vim.inspect(b)) + )) + end +end + +local function format_message_with_content_length(encoded_message) + return table.concat { + 'Content-Length: '; tostring(#encoded_message); '\r\n\r\n'; + encoded_message; + } +end + +-- Server utility methods. + +local function read_message() + local line = io.read("*l") + local length = line:lower():match("content%-length:%s*(%d+)") + return assert(json_decode(io.read(2 + length):sub(2)), "read_message.json_decode") +end + +local function send(payload) + io.stdout:write(format_message_with_content_length(json_encode(payload))) +end + +local function respond(id, err, result) + assert(type(id) == 'number', "id must be a number") + send { jsonrpc = "2.0"; id = id, error = err, result = result } +end + +local function notify(method, params) + assert(type(method) == 'string', "method must be a string") + send { method = method, params = params or {} } +end + +local function expect_notification(method, params, ...) + local message = read_message() + assert_eq(method, message.method, + ..., "expect_notification", "method") + assert_eq(params, message.params, + ..., "expect_notification", method, "params") + assert_eq({jsonrpc = "2.0"; method=method, params=params}, message, + ..., "expect_notification", "message") +end + +local function expect_request(method, callback, ...) + local req = read_message() + assert_eq(method, req.method, + ..., "expect_request", "method") + local err, result = callback(req.params) + respond(req.id, err, result) +end + +io.stderr:setvbuf("no") + +local function skeleton(config) + local on_init = assert(config.on_init) + local body = assert(config.body) + expect_request("initialize", function(params) + return nil, on_init(params) + end) + expect_notification("initialized", {}) + body() + expect_request("shutdown", function() + return nil, {} + end) + expect_notification("exit", nil) +end + +-- The actual tests. + +local tests = {} + +function tests.basic_init() + skeleton { + on_init = function(_params) + return { capabilities = {} } + end; + body = function() + notify('test') + end; + } +end + +function tests.basic_check_capabilities() + skeleton { + on_init = function(params) + local expected_capabilities = protocol.make_client_capabilities() + assert_eq(params.capabilities, expected_capabilities) + return { + capabilities = { + textDocumentSync = protocol.TextDocumentSyncKind.Full; + } + } + end; + body = function() + end; + } +end + +function tests.basic_finish() + skeleton { + on_init = function(params) + local expected_capabilities = protocol.make_client_capabilities() + assert_eq(params.capabilities, expected_capabilities) + return { + capabilities = { + textDocumentSync = protocol.TextDocumentSyncKind.Full; + } + } + end; + body = function() + expect_notification("finish") + notify('finish') + end; + } +end + +function tests.basic_check_buffer_open() + skeleton { + on_init = function(params) + local expected_capabilities = protocol.make_client_capabilities() + assert_eq(params.capabilities, expected_capabilities) + return { + capabilities = { + textDocumentSync = protocol.TextDocumentSyncKind.Full; + } + } + end; + body = function() + notify('start') + expect_notification('textDocument/didOpen', { + textDocument = { + languageId = ""; + text = table.concat({"testing"; "123"}, "\n"); + uri = "file://"; + version = 0; + }; + }) + expect_notification("finish") + notify('finish') + end; + } +end + +function tests.basic_check_buffer_open_and_change() + skeleton { + on_init = function(params) + local expected_capabilities = protocol.make_client_capabilities() + assert_eq(params.capabilities, expected_capabilities) + return { + capabilities = { + textDocumentSync = protocol.TextDocumentSyncKind.Full; + } + } + end; + body = function() + notify('start') + expect_notification('textDocument/didOpen', { + textDocument = { + languageId = ""; + text = table.concat({"testing"; "123"}, "\n"); + uri = "file://"; + version = 0; + }; + }) + expect_notification('textDocument/didChange', { + textDocument = { + uri = "file://"; + version = 3; + }; + contentChanges = { + { text = table.concat({"testing"; "boop"}, "\n"); }; + } + }) + expect_notification("finish") + notify('finish') + end; + } +end + +function tests.basic_check_buffer_open_and_change_multi() + skeleton { + on_init = function(params) + local expected_capabilities = protocol.make_client_capabilities() + assert_eq(params.capabilities, expected_capabilities) + return { + capabilities = { + textDocumentSync = protocol.TextDocumentSyncKind.Full; + } + } + end; + body = function() + notify('start') + expect_notification('textDocument/didOpen', { + textDocument = { + languageId = ""; + text = table.concat({"testing"; "123"}, "\n"); + uri = "file://"; + version = 0; + }; + }) + expect_notification('textDocument/didChange', { + textDocument = { + uri = "file://"; + version = 3; + }; + contentChanges = { + { text = table.concat({"testing"; "321"}, "\n"); }; + } + }) + expect_notification('textDocument/didChange', { + textDocument = { + uri = "file://"; + version = 4; + }; + contentChanges = { + { text = table.concat({"testing"; "boop"}, "\n"); }; + } + }) + expect_notification("finish") + notify('finish') + end; + } +end + +function tests.basic_check_buffer_open_and_change_multi_and_close() + skeleton { + on_init = function(params) + local expected_capabilities = protocol.make_client_capabilities() + assert_eq(params.capabilities, expected_capabilities) + return { + capabilities = { + textDocumentSync = protocol.TextDocumentSyncKind.Full; + } + } + end; + body = function() + notify('start') + expect_notification('textDocument/didOpen', { + textDocument = { + languageId = ""; + text = table.concat({"testing"; "123"}, "\n"); + uri = "file://"; + version = 0; + }; + }) + expect_notification('textDocument/didChange', { + textDocument = { + uri = "file://"; + version = 3; + }; + contentChanges = { + { text = table.concat({"testing"; "321"}, "\n"); }; + } + }) + expect_notification('textDocument/didChange', { + textDocument = { + uri = "file://"; + version = 4; + }; + contentChanges = { + { text = table.concat({"testing"; "boop"}, "\n"); }; + } + }) + expect_notification('textDocument/didClose', { + textDocument = { + uri = "file://"; + }; + }) + expect_notification("finish") + notify('finish') + end; + } +end + +function tests.basic_check_buffer_open_and_change_incremental() + skeleton { + on_init = function(params) + local expected_capabilities = protocol.make_client_capabilities() + assert_eq(params.capabilities, expected_capabilities) + return { + capabilities = { + textDocumentSync = protocol.TextDocumentSyncKind.Incremental; + } + } + end; + body = function() + notify('start') + expect_notification('textDocument/didOpen', { + textDocument = { + languageId = ""; + text = table.concat({"testing"; "123"}, "\n"); + uri = "file://"; + version = 0; + }; + }) + expect_notification('textDocument/didChange', { + textDocument = { + uri = "file://"; + version = 3; + }; + contentChanges = { + { + range = { + start = { line = 1; character = 0; }; + ["end"] = { line = 2; character = 0; }; + }; + rangeLength = 4; + text = "boop\n"; + }; + } + }) + expect_notification("finish") + notify('finish') + end; + } +end + +function tests.basic_check_buffer_open_and_change_incremental_editting() + skeleton { + on_init = function(params) + local expected_capabilities = protocol.make_client_capabilities() + assert_eq(params.capabilities, expected_capabilities) + return { + capabilities = { + textDocumentSync = protocol.TextDocumentSyncKind.Incremental; + } + } + end; + body = function() + notify('start') + expect_notification('textDocument/didOpen', { + textDocument = { + languageId = ""; + text = table.concat({"testing"; "123"}, "\n"); + uri = "file://"; + version = 0; + }; + }) + expect_notification('textDocument/didChange', { + textDocument = { + uri = "file://"; + version = 3; + }; + contentChanges = { + { + range = { + start = { line = 0; character = 0; }; + ["end"] = { line = 1; character = 0; }; + }; + rangeLength = 4; + text = "testing\n\n"; + }; + } + }) + expect_notification("finish") + notify('finish') + end; + } +end + +function tests.invalid_header() + io.stdout:write("Content-length: \r\n") +end + +-- Tests will be indexed by TEST_NAME + +local kill_timer = vim.loop.new_timer() +kill_timer:start(_G.TIMEOUT or 1e3, 0, function() + kill_timer:stop() + kill_timer:close() + io.stderr:write("TIMEOUT") + os.exit(100) +end) + +local test_name = _G.TEST_NAME -- lualint workaround +assert(type(test_name) == 'string', 'TEST_NAME must be specified.') +local status, err = pcall(assert(tests[test_name], "Test not found")) +kill_timer:stop() +kill_timer:close() +if not status then + io.stderr:write(err) + os.exit(1) +end +os.exit(0) diff --git a/test/functional/legacy/022_line_ending_spec.lua b/test/functional/legacy/022_line_ending_spec.lua deleted file mode 100644 index fb4b782011..0000000000 --- a/test/functional/legacy/022_line_ending_spec.lua +++ /dev/null @@ -1,25 +0,0 @@ --- Tests for file with some lines ending in CTRL-M, some not - -local helpers = require('test.functional.helpers')(after_each) -local clear, feed = helpers.clear, helpers.feed -local feed_command, expect = helpers.feed_command, helpers.expect - -describe('line ending', function() - setup(clear) - - it('is working', function() - feed('i', [[ - this lines ends in a<C-V><C-M> - this one doesn't - this one does<C-V><C-M> - and the last one doesn't]], '<ESC>') - - feed_command('set ta tx') - feed_command('e!') - - expect("this lines ends in a\r\n".. - "this one doesn't\n".. - "this one does\r\n".. - "and the last one doesn't") - end) -end) diff --git a/test/functional/legacy/041_writing_and_reading_hundred_kbyte_spec.lua b/test/functional/legacy/041_writing_and_reading_hundred_kbyte_spec.lua deleted file mode 100644 index b526d82519..0000000000 --- a/test/functional/legacy/041_writing_and_reading_hundred_kbyte_spec.lua +++ /dev/null @@ -1,43 +0,0 @@ --- Test for writing and reading a file of over 100 Kbyte - -local helpers = require('test.functional.helpers')(after_each) - -local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert -local command, expect = helpers.command, helpers.expect -local wait = helpers.wait - -describe('writing and reading a file of over 100 Kbyte', function() - setup(clear) - - it('is working', function() - insert([[ - This is the start - This is the leader - This is the middle - This is the trailer - This is the end]]) - - feed('kY3000p2GY3000p') - wait() - - command('w! test.out') - command('%d') - command('e! test.out') - command('yank A') - command('3003yank A') - command('6005yank A') - command('%d') - command('0put a') - command('$d') - command('w!') - - expect([[ - This is the start - This is the middle - This is the end]]) - end) - - teardown(function() - os.remove('test.out') - end) -end) diff --git a/test/functional/legacy/077_mf_hash_grow_spec.lua b/test/functional/legacy/077_mf_hash_grow_spec.lua deleted file mode 100644 index 4719a3ecbf..0000000000 --- a/test/functional/legacy/077_mf_hash_grow_spec.lua +++ /dev/null @@ -1,52 +0,0 @@ --- Inserts 2 million lines with consecutive integers starting from 1 --- (essentially, the output of GNU's seq 1 2000000), writes them to Xtest --- and calculates its cksum. --- We need 2 million lines to trigger a call to mf_hash_grow(). If it would mess --- up the lines the checksum would differ. --- cksum is part of POSIX and so should be available on most Unixes. --- If it isn't available then the test will be skipped. - -local helpers = require('test.functional.helpers')(after_each) - -local feed = helpers.feed -local wait = helpers.wait -local clear = helpers.clear -local expect = helpers.expect -local command = helpers.command - -describe('mf_hash_grow()', function() - setup(clear) - - -- Check to see if cksum exists, otherwise skip the test - local null = helpers.iswin() and 'nul' or '/dev/null' - if os.execute('cksum --help >' .. null .. ' 2>&1') ~= 0 then - pending('was not tested because cksum was not found', function() end) - else - it('is working', function() - command('set fileformat=unix undolevels=-1') - - -- Fill the buffer with numbers 1 - 2000000 - command('let i = 1') - command('while i <= 2000000 | call append(i, range(i, i + 99)) | let i += 100 | endwhile') - - -- Delete empty first line, save to Xtest, and clear buffer - feed('ggdd<cr>') - wait() - command('w! Xtest') - feed('ggdG<cr>') - wait() - - -- Calculate the cksum of Xtest and delete first line - command('r !cksum Xtest') - feed('ggdd<cr>') - - -- Assert correct output of cksum. - expect([[ - 3678979763 14888896 Xtest]]) - end) - end - - teardown(function() - os.remove('Xtest') - end) -end) diff --git a/test/functional/legacy/084_curswant_spec.lua b/test/functional/legacy/084_curswant_spec.lua deleted file mode 100644 index 42cb2fc56d..0000000000 --- a/test/functional/legacy/084_curswant_spec.lua +++ /dev/null @@ -1,49 +0,0 @@ --- Tests for curswant not changing when setting an option. - -local helpers = require('test.functional.helpers')(after_each) -local insert, source = helpers.insert, helpers.source -local clear, expect = helpers.clear, helpers.expect - -describe('curswant', function() - setup(clear) - - -- luacheck: ignore 621 (Indentation) - it('is working', function() - insert([[ - start target options - tabstop - timeoutlen - ttimeoutlen - end target options]]) - - source([[ - /^start target options$/+1,/^end target options$/-1 yank - let target_option_names = split(@0) - function TestCurswant(option_name) - normal! ggf8j - let curswant_before = winsaveview().curswant - execute 'let' '&'.a:option_name '=' '&'.a:option_name - let curswant_after = winsaveview().curswant - return [a:option_name, curswant_before, curswant_after] - endfunction - - new - put =['1234567890', '12345'] - 1 delete _ - let result = [] - for option_name in target_option_names - call add(result, TestCurswant(option_name)) - endfor - - new - put =map(copy(result), 'join(v:val, '' '')') - 1 delete _ - ]]) - - -- Assert buffer contents. - expect([[ - tabstop 7 4 - timeoutlen 7 7 - ttimeoutlen 7 7]]) - end) -end) diff --git a/test/functional/legacy/098_scrollbind_spec.lua b/test/functional/legacy/098_scrollbind_spec.lua deleted file mode 100644 index d22aefdcbc..0000000000 --- a/test/functional/legacy/098_scrollbind_spec.lua +++ /dev/null @@ -1,48 +0,0 @@ --- Test for 'scrollbind' causing an unexpected scroll of one of the windows. - -local helpers = require('test.functional.helpers')(after_each) -local source = helpers.source -local clear, expect = helpers.clear, helpers.expect - -describe('scrollbind', function() - setup(clear) - - it('is working', function() - source([[ - set laststatus=0 - let g:totalLines = &lines * 20 - let middle = g:totalLines / 2 - wincmd n - wincmd o - for i in range(1, g:totalLines) - call setline(i, 'LINE ' . i) - endfor - exe string(middle) - normal zt - normal M - aboveleft vert new - for i in range(1, g:totalLines) - call setline(i, 'line ' . i) - endfor - exe string(middle) - normal zt - normal M - setl scb | wincmd p - setl scb - wincmd w - let topLineLeft = line('w0') - wincmd p - let topLineRight = line('w0') - setl noscrollbind - wincmd p - setl noscrollbind - q! - %del _ - call setline(1, 'Difference between the top lines (left - right): ' . string(topLineLeft - topLineRight)) - brewind - ]]) - - -- Assert buffer contents. - expect("Difference between the top lines (left - right): 0") - end) -end) diff --git a/test/functional/legacy/104_let_assignment_spec.lua b/test/functional/legacy/104_let_assignment_spec.lua deleted file mode 100644 index a03bb026f6..0000000000 --- a/test/functional/legacy/104_let_assignment_spec.lua +++ /dev/null @@ -1,54 +0,0 @@ --- Tests for :let. - -local helpers = require('test.functional.helpers')(after_each) -local clear, source = helpers.clear, helpers.source -local command, expect = helpers.command, helpers.expect - -describe(':let', function() - setup(clear) - - it('is working', function() - command('set runtimepath+=test/functional/fixtures') - - -- Test to not autoload when assigning. It causes internal error. - source([[ - try - let Test104#numvar = function('tr') - $put ='OK: ' . string(Test104#numvar) - catch - $put ='FAIL: ' . v:exception - endtry - let a = 1 - let b = 2 - for letargs in ['a b', '{0 == 1 ? "a" : "b"}', '{0 == 1 ? "a" : "b"} a', 'a {0 == 1 ? "a" : "b"}'] - try - redir => messages - execute 'let' letargs - redir END - $put ='OK:' - $put =split(substitute(messages, '\n', '\0 ', 'g'), '\n') - catch - $put ='FAIL: ' . v:exception - redir END - endtry - endfor]]) - - -- Remove empty line - command('1d') - - -- Assert buffer contents. - expect([[ - OK: function('tr') - OK: - a #1 - b #2 - OK: - b #2 - OK: - b #2 - a #1 - OK: - a #1 - b #2]]) - end) -end) diff --git a/test/functional/lua/api_spec.lua b/test/functional/lua/api_spec.lua index b1dc5c07fd..23167d3ed9 100644 --- a/test/functional/lua/api_spec.lua +++ b/test/functional/lua/api_spec.lua @@ -155,41 +155,41 @@ describe('luaeval(vim.api.…)', function() it('errors out correctly when working with API', function() -- Conversion errors - eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<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/commands_spec.lua b/test/functional/lua/commands_spec.lua index 26dcbe0534..96eaa7991b 100644 --- a/test/functional/lua/commands_spec.lua +++ b/test/functional/lua/commands_spec.lua @@ -13,6 +13,7 @@ local source = helpers.source local dedent = helpers.dedent local command = helpers.command local exc_exec = helpers.exc_exec +local pcall_err = helpers.pcall_err local write_file = helpers.write_file local redir_exec = helpers.redir_exec local curbufmeths = helpers.curbufmeths @@ -42,16 +43,16 @@ describe(':lua command', function() eq({'', 'ETTS', 'TTSE', 'STTE'}, curbufmeths.get_lines(0, 100, false)) end) it('throws catchable errors', function() - eq([[Vim(lua):E5104: Error while creating lua chunk: [string "<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]], 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..61c8e5c02e 100644 --- a/test/functional/lua/luaeval_spec.lua +++ b/test/functional/lua/luaeval_spec.lua @@ -1,13 +1,17 @@ -- Test suite for testing luaeval() function local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local redir_exec = helpers.redir_exec +local pcall_err = helpers.pcall_err local exc_exec = helpers.exc_exec +local exec_lua = helpers.exec_lua local command = helpers.command local meths = helpers.meths local funcs = helpers.funcs local clear = helpers.clear local eval = helpers.eval +local feed = helpers.feed local NIL = helpers.NIL local eq = helpers.eq @@ -186,9 +190,9 @@ describe('luaeval()', function() exc_exec('call luaeval("{1, foo=2}")')) eq("Vim(call):E5101: Cannot convert given lua type", exc_exec('call luaeval("vim.api.nvim_buf_get_lines")')) - startswith("Vim(call):E5107: Error while creating lua chunk for luaeval(): ", + startswith("Vim(call):E5107: Error loading lua [string \"luaeval()\"]:", exc_exec('call luaeval("1, 2, 3")')) - startswith("Vim(call):E5108: Error while calling lua chunk for luaeval(): ", + startswith("Vim(call):E5108: Error executing lua [string \"luaeval()\"]:", exc_exec('call luaeval("(nil)()")')) eq("Vim(call):E5101: Cannot convert given lua type", exc_exec('call luaeval("{42, vim.api}")')) @@ -237,19 +241,99 @@ describe('luaeval()', function() it('errors out correctly when doing incorrect things in lua', function() -- Conversion errors - eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<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..8c260632d9 100644 --- a/test/functional/lua/overrides_spec.lua +++ b/test/functional/lua/overrides_spec.lua @@ -54,11 +54,12 @@ describe('print', function() v_tblout = setmetatable({}, meta_tblout) ]]) eq('', redir_exec('luafile ' .. fname)) - eq('\nE5105: Error while calling lua chunk: E5114: Error while converting print argument #2: [NULL]', + -- TODO(bfredl): these look weird, print() should not use "E5114:" style errors.. + eq('\nE5108: Error executing lua E5114: Error while converting print argument #2: [NULL]', redir_exec('lua print("foo", v_nilerr, "bar")')) - eq('\nE5105: Error while calling lua chunk: E5114: Error while converting print argument #2: Xtest-functional-lua-overrides-luafile:2: abc', + eq('\nE5108: Error executing lua E5114: Error while converting print argument #2: Xtest-functional-lua-overrides-luafile:2: abc', redir_exec('lua print("foo", v_abcerr, "bar")')) - eq('\nE5105: Error while calling lua chunk: E5114: Error while converting print argument #2: <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() diff --git a/test/functional/lua/treesitter_spec.lua b/test/functional/lua/treesitter_spec.lua index 700e4599f2..5a53ca1425 100644 --- a/test/functional/lua/treesitter_spec.lua +++ b/test/functional/lua/treesitter_spec.lua @@ -15,9 +15,7 @@ before_each(clear) describe('treesitter API', function() -- error tests not requiring a parser library it('handles missing language', function() - local path_pat = 'Error executing lua: '..(iswin() and '.+\\vim\\' or '.+/vim/') - - matches(path_pat..'treesitter.lua:39: no such language: borklang', + eq('Error executing lua: .../treesitter.lua: no such language: borklang', pcall_err(exec_lua, "parser = vim.treesitter.create_parser(0, 'borklang')")) -- actual message depends on platform diff --git a/test/functional/lua/uri_spec.lua b/test/functional/lua/uri_spec.lua new file mode 100644 index 0000000000..19b1eb1f61 --- /dev/null +++ b/test/functional/lua/uri_spec.lua @@ -0,0 +1,107 @@ +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 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) + 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..028f2dcd52 --- /dev/null +++ b/test/functional/lua/vim_spec.lua @@ -0,0 +1,552 @@ +-- 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 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.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 ', ' ' } }, + { "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() + 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'))]])) + + -- 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_islist', function() + eq(NIL, exec_lua("return vim.tbl_islist({})")) + 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.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 must be a table', + 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 ]]) + 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]])) + + -- 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([[ + local 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')) + 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) +end) diff --git a/test/functional/plugin/lsp/lsp_spec.lua b/test/functional/plugin/lsp/lsp_spec.lua new file mode 100644 index 0000000000..cd0974b81c --- /dev/null +++ b/test/functional/plugin/lsp/lsp_spec.lua @@ -0,0 +1,634 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local exec_lua = helpers.exec_lua +local eq = helpers.eq +local NIL = helpers.NIL + +-- Use these to get access to a coroutine so that I can run async tests and use +-- yield. +local run, stop = helpers.run, helpers.stop + +if helpers.pending_win32(pending) then return end + +local is_windows = require'luv'.os_uname().sysname == "Windows" +local lsp_test_rpc_server_file = "test/functional/fixtures/lsp-test-rpc-server.lua" +if is_windows then + lsp_test_rpc_server_file = lsp_test_rpc_server_file:gsub("/", "\\") +end + +local function test_rpc_server_setup(test_name, timeout_ms) + exec_lua([=[ + lsp = require('vim.lsp') + local test_name, fixture_filename, timeout = ... + TEST_RPC_CLIENT_ID = lsp.start_client { + cmd = { + vim.api.nvim_get_vvar("progpath"), '-Es', '-u', 'NONE', '--headless', + "-c", string.format("lua TEST_NAME = %q", test_name), + "-c", string.format("lua TIMEOUT = %d", timeout), + "-c", "luafile "..fixture_filename, + }; + callbacks = setmetatable({}, { + __index = function(t, method) + return function(...) + return vim.rpcrequest(1, 'callback', ...) + end + end; + }); + root_dir = vim.loop.cwd(); + on_init = function(client, result) + TEST_RPC_CLIENT = client + vim.rpcrequest(1, "init", result) + end; + on_exit = function(...) + vim.rpcnotify(1, "exit", ...) + end; + } + ]=], test_name, lsp_test_rpc_server_file, timeout_ms or 1e3) +end + +local function test_rpc_server(config) + if config.test_name then + clear() + test_rpc_server_setup(config.test_name, config.timeout_ms or 1e3) + end + local client = setmetatable({}, { + __index = function(_, name) + -- Workaround for not being able to yield() inside __index for Lua 5.1 :( + -- Otherwise I would just return the value here. + return function(...) + return exec_lua([=[ + local name = ... + if type(TEST_RPC_CLIENT[name]) == 'function' then + return TEST_RPC_CLIENT[name](select(2, ...)) + else + return TEST_RPC_CLIENT[name] + end + ]=], name, ...) + end + end; + }) + local code, signal + local function on_request(method, args) + if method == "init" then + if config.on_init then + config.on_init(client, unpack(args)) + end + return NIL + end + if method == 'callback' then + if config.on_callback then + config.on_callback(unpack(args)) + end + end + return NIL + end + local function on_notify(method, args) + if method == 'exit' then + code, signal = unpack(args) + return stop() + end + end + -- TODO specify timeout? + -- run(on_request, on_notify, config.on_setup, 1000) + run(on_request, on_notify, config.on_setup) + if config.on_exit then + config.on_exit(code, signal) + end + stop() + if config.test_name then + exec_lua("lsp._vim_exit_handler()") + end +end + +describe('Language Client API', function() + describe('server_name is specified', function() + before_each(function() + clear() + -- Run an instance of nvim on the file which contains our "scripts". + -- Pass TEST_NAME to pick the script. + local test_name = "basic_init" + exec_lua([=[ + lsp = require('vim.lsp') + local test_name, fixture_filename = ... + TEST_RPC_CLIENT_ID = lsp.start_client { + cmd = { + vim.api.nvim_get_vvar("progpath"), '-Es', '-u', 'NONE', '--headless', + "-c", string.format("lua TEST_NAME = %q", test_name), + "-c", "luafile "..fixture_filename; + }; + root_dir = vim.loop.cwd(); + } + ]=], test_name, lsp_test_rpc_server_file) + end) + + after_each(function() + exec_lua("lsp._vim_exit_handler()") + -- exec_lua("lsp.stop_all_clients(true)") + end) + + describe('start_client and stop_client', function() + it('should return true', function() + for _ = 1, 20 do + helpers.sleep(10) + if exec_lua("return #lsp.get_active_clients()") > 0 then + break + end + end + eq(1, exec_lua("return #lsp.get_active_clients()")) + eq(false, exec_lua("return lsp.get_client_by_id(TEST_RPC_CLIENT_ID) == nil")) + eq(false, exec_lua("return lsp.get_client_by_id(TEST_RPC_CLIENT_ID).is_stopped()")) + exec_lua("return lsp.get_client_by_id(TEST_RPC_CLIENT_ID).stop()") + eq(false, exec_lua("return lsp.get_client_by_id(TEST_RPC_CLIENT_ID).is_stopped()")) + for _ = 1, 20 do + helpers.sleep(10) + if exec_lua("return #lsp.get_active_clients()") == 0 then + break + end + end + eq(true, exec_lua("return lsp.get_client_by_id(TEST_RPC_CLIENT_ID) == nil")) + end) + end) + end) + + describe('basic_init test', function() + it('should run correctly', function() + local expected_callbacks = { + {NIL, "test", {}, 1}; + } + test_rpc_server { + test_name = "basic_init"; + on_init = function(client, _init_result) + -- client is a dummy object which will queue up commands to be run + -- once the server initializes. It can't accept lua callbacks or + -- other types that may be unserializable for now. + client.stop() + end; + -- If the program timed out, then code will be nil. + on_exit = function(code, signal) + eq(0, code, "exit code") eq(0, signal, "exit signal") + end; + -- Note that NIL must be used here. + -- on_callback(err, method, result, client_id) + on_callback = function(...) + eq(table.remove(expected_callbacks), {...}) + end; + } + end) + + it('should fail', function() + local expected_callbacks = { + {NIL, "test", {}, 1}; + } + test_rpc_server { + test_name = "basic_init"; + on_init = function(client) + client.notify('test') + client.stop() + end; + on_exit = function(code, signal) + eq(1, code, "exit code") eq(0, signal, "exit signal") + end; + on_callback = function(...) + eq(table.remove(expected_callbacks), {...}, "expected callback") + end; + } + end) + + it('should succeed with manual shutdown', function() + local expected_callbacks = { + {NIL, "shutdown", {}, 1}; + {NIL, "test", {}, 1}; + } + test_rpc_server { + test_name = "basic_init"; + on_init = function(client) + eq(0, client.resolved_capabilities().text_document_did_change) + client.request('shutdown') + client.notify('exit') + end; + on_exit = function(code, signal) + eq(0, code, "exit code") eq(0, signal, "exit signal") + end; + on_callback = function(...) + eq(table.remove(expected_callbacks), {...}, "expected callback") + end; + } + end) + + it('should verify capabilities sent', function() + local expected_callbacks = { + {NIL, "shutdown", {}, 1}; + } + test_rpc_server { + test_name = "basic_check_capabilities"; + on_init = function(client) + client.stop() + end; + on_exit = function(code, signal) + eq(0, code, "exit code") eq(0, signal, "exit signal") + end; + on_callback = function(...) + eq(table.remove(expected_callbacks), {...}, "expected callback") + end; + } + end) + + it('should not send didOpen if the buffer closes before init', function() + local expected_callbacks = { + {NIL, "shutdown", {}, 1}; + {NIL, "finish", {}, 1}; + } + local client + test_rpc_server { + test_name = "basic_finish"; + on_setup = function() + exec_lua [[ + BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { + "testing"; + "123"; + }) + ]] + eq(1, exec_lua("return TEST_RPC_CLIENT_ID")) + eq(true, exec_lua("return lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)")) + eq(true, exec_lua("return lsp.buf_is_attached(BUFFER, TEST_RPC_CLIENT_ID)")) + exec_lua [[ + vim.api.nvim_command(BUFFER.."bwipeout") + ]] + end; + on_init = function(_client) + client = _client + local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + eq(full_kind, client.resolved_capabilities().text_document_did_change) + eq(true, client.resolved_capabilities().text_document_open_close) + client.notify('finish') + end; + on_exit = function(code, signal) + eq(0, code, "exit code") eq(0, signal, "exit signal") + end; + on_callback = function(err, method, params, client_id) + eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback") + if method == 'finish' then + client.stop() + end + end; + } + end) + + it('should check the body sent attaching before init', function() + local expected_callbacks = { + {NIL, "shutdown", {}, 1}; + {NIL, "finish", {}, 1}; + {NIL, "start", {}, 1}; + } + local client + test_rpc_server { + test_name = "basic_check_buffer_open"; + on_setup = function() + exec_lua [[ + BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { + "testing"; + "123"; + }) + ]] + exec_lua [[ + assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) + ]] + end; + on_init = function(_client) + client = _client + local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + eq(full_kind, client.resolved_capabilities().text_document_did_change) + eq(true, client.resolved_capabilities().text_document_open_close) + exec_lua [[ + assert(not lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID), "Shouldn't attach twice") + ]] + end; + on_exit = function(code, signal) + eq(0, code, "exit code") eq(0, signal, "exit signal") + end; + on_callback = function(err, method, params, client_id) + if method == 'start' then + client.notify('finish') + end + eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback") + if method == 'finish' then + client.stop() + end + end; + } + end) + + it('should check the body sent attaching after init', function() + local expected_callbacks = { + {NIL, "shutdown", {}, 1}; + {NIL, "finish", {}, 1}; + {NIL, "start", {}, 1}; + } + local client + test_rpc_server { + test_name = "basic_check_buffer_open"; + on_setup = function() + exec_lua [[ + BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { + "testing"; + "123"; + }) + ]] + end; + on_init = function(_client) + client = _client + local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + eq(full_kind, client.resolved_capabilities().text_document_did_change) + eq(true, client.resolved_capabilities().text_document_open_close) + exec_lua [[ + assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) + ]] + end; + on_exit = function(code, signal) + eq(0, code, "exit code") eq(0, signal, "exit signal") + end; + on_callback = function(err, method, params, client_id) + if method == 'start' then + client.notify('finish') + end + eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback") + if method == 'finish' then + client.stop() + end + end; + } + end) + + it('should check the body and didChange full', function() + local expected_callbacks = { + {NIL, "shutdown", {}, 1}; + {NIL, "finish", {}, 1}; + {NIL, "start", {}, 1}; + } + local client + test_rpc_server { + test_name = "basic_check_buffer_open_and_change"; + on_setup = function() + exec_lua [[ + BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { + "testing"; + "123"; + }) + ]] + end; + on_init = function(_client) + client = _client + local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + eq(full_kind, client.resolved_capabilities().text_document_did_change) + eq(true, client.resolved_capabilities().text_document_open_close) + exec_lua [[ + assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) + ]] + end; + on_exit = function(code, signal) + eq(0, code, "exit code") eq(0, signal, "exit signal") + end; + on_callback = function(err, method, params, client_id) + if method == 'start' then + exec_lua [[ + vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { + "boop"; + }) + ]] + client.notify('finish') + end + eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback") + if method == 'finish' then + client.stop() + end + end; + } + end) + + -- TODO(askhan) we don't support full for now, so we can disable these tests. + pending('should check the body and didChange incremental', function() + local expected_callbacks = { + {NIL, "shutdown", {}, 1}; + {NIL, "finish", {}, 1}; + {NIL, "start", {}, 1}; + } + local client + test_rpc_server { + test_name = "basic_check_buffer_open_and_change_incremental"; + on_setup = function() + exec_lua [[ + BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { + "testing"; + "123"; + }) + ]] + end; + on_init = function(_client) + client = _client + local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Incremental") + eq(sync_kind, client.resolved_capabilities().text_document_did_change) + eq(true, client.resolved_capabilities().text_document_open_close) + exec_lua [[ + assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) + ]] + end; + on_exit = function(code, signal) + eq(0, code, "exit code") eq(0, signal, "exit signal") + end; + on_callback = function(err, method, params, client_id) + if method == 'start' then + exec_lua [[ + vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { + "boop"; + }) + ]] + client.notify('finish') + end + eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback") + if method == 'finish' then + client.stop() + end + end; + } + end) + + -- TODO(askhan) we don't support full for now, so we can disable these tests. + pending('should check the body and didChange incremental normal mode editting', function() + local expected_callbacks = { + {NIL, "shutdown", {}, 1}; + {NIL, "finish", {}, 1}; + {NIL, "start", {}, 1}; + } + local client + test_rpc_server { + test_name = "basic_check_buffer_open_and_change_incremental_editting"; + on_setup = function() + exec_lua [[ + BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { + "testing"; + "123"; + }) + ]] + end; + on_init = function(_client) + client = _client + local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Incremental") + eq(sync_kind, client.resolved_capabilities().text_document_did_change) + eq(true, client.resolved_capabilities().text_document_open_close) + exec_lua [[ + assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) + ]] + end; + on_exit = function(code, signal) + eq(0, code, "exit code") eq(0, signal, "exit signal") + end; + on_callback = function(err, method, params, client_id) + if method == 'start' then + helpers.command("normal! 1Go") + client.notify('finish') + end + eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback") + if method == 'finish' then + client.stop() + end + end; + } + end) + + it('should check the body and didChange full with 2 changes', function() + local expected_callbacks = { + {NIL, "shutdown", {}, 1}; + {NIL, "finish", {}, 1}; + {NIL, "start", {}, 1}; + } + local client + test_rpc_server { + test_name = "basic_check_buffer_open_and_change_multi"; + on_setup = function() + exec_lua [[ + BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { + "testing"; + "123"; + }) + ]] + end; + on_init = function(_client) + client = _client + local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + eq(sync_kind, client.resolved_capabilities().text_document_did_change) + eq(true, client.resolved_capabilities().text_document_open_close) + exec_lua [[ + assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) + ]] + end; + on_exit = function(code, signal) + eq(0, code, "exit code") eq(0, signal, "exit signal") + end; + on_callback = function(err, method, params, client_id) + if method == 'start' then + exec_lua [[ + vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { + "321"; + }) + vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { + "boop"; + }) + ]] + client.notify('finish') + end + eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback") + if method == 'finish' then + client.stop() + end + end; + } + end) + + it('should check the body and didChange full lifecycle', function() + local expected_callbacks = { + {NIL, "shutdown", {}, 1}; + {NIL, "finish", {}, 1}; + {NIL, "start", {}, 1}; + } + local client + test_rpc_server { + test_name = "basic_check_buffer_open_and_change_multi_and_close"; + on_setup = function() + exec_lua [[ + BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { + "testing"; + "123"; + }) + ]] + end; + on_init = function(_client) + client = _client + local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + eq(sync_kind, client.resolved_capabilities().text_document_did_change) + eq(true, client.resolved_capabilities().text_document_open_close) + exec_lua [[ + assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) + ]] + end; + on_exit = function(code, signal) + eq(0, code, "exit code") eq(0, signal, "exit signal") + end; + on_callback = function(err, method, params, client_id) + if method == 'start' then + exec_lua [[ + vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { + "321"; + }) + vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { + "boop"; + }) + vim.api.nvim_command(BUFFER.."bwipeout") + ]] + client.notify('finish') + end + eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback") + if method == 'finish' then + client.stop() + end + end; + } + end) + + end) + + describe("parsing tests", function() + it('should handle invalid content-length correctly', function() + local expected_callbacks = { + {NIL, "shutdown", {}, 1}; + {NIL, "finish", {}, 1}; + {NIL, "start", {}, 1}; + } + local client + test_rpc_server { + test_name = "invalid_header"; + on_setup = function() + end; + on_init = function(_client) + client = _client + client.stop(true) + end; + on_exit = function(code, signal) + eq(0, code, "exit code") eq(0, signal, "exit signal") + end; + on_callback = function(err, method, params, client_id) + eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback") + end; + } + end) + + end) +end) diff --git a/test/functional/terminal/highlight_spec.lua b/test/functional/terminal/highlight_spec.lua index 06a6fd6f2b..8d3f0218af 100644 --- a/test/functional/terminal/highlight_spec.lua +++ b/test/functional/terminal/highlight_spec.lua @@ -121,13 +121,12 @@ it(':terminal highlight has lower precedence than editor #9964', function() local screen = Screen.new(30, 4) screen:set_default_attr_ids({ -- "Normal" highlight emitted by the child nvim process. - N_child = {foreground = tonumber('0x4040ff'), background = tonumber('0xffff40')}, - -- "Search" highlight emitted by the child nvim process. - S_child = {background = tonumber('0xffff40'), italic = true, foreground = tonumber('0x4040ff')}, + N_child = {foreground = tonumber('0x4040ff'), background = tonumber('0xffff40'), fg_indexed=true, bg_indexed=true}, -- "Search" highlight in the parent nvim process. S = {background = Screen.colors.Green, italic = true, foreground = Screen.colors.Red}, -- "Question" highlight in the parent nvim process. - Q = {background = tonumber('0xffff40'), bold = true, foreground = Screen.colors.SeaGreen4}, + -- note: bg is indexed as it comes from the (cterm) child, while fg isn't as it comes from (rgb) parent + Q = {background = tonumber('0xffff40'), bold = true, foreground = Screen.colors.SeaGreen4, bg_indexed=true}, }) screen:attach({rgb=true}) -- Child nvim process in :terminal (with cterm colors). @@ -160,6 +159,54 @@ it(':terminal highlight has lower precedence than editor #9964', function() ]]) end) +describe(':terminal highlight forwarding', function() + local screen + + before_each(function() + clear() + screen = Screen.new(50, 7) + screen:set_rgb_cterm(true) + screen:set_default_attr_ids({ + [1] = {{reverse = true}, {reverse = true}}, + [2] = {{bold = true}, {bold = true}}, + [3] = {{fg_indexed = true, foreground = tonumber('0xe0e000')}, {foreground = 3}}, + [4] = {{foreground = tonumber('0xff8000')}, {}}, + }) + screen:attach() + command('enew | call termopen(["'..nvim_dir..'/tty-test"])') + feed('i') + screen:expect([[ + tty ready | + {1: } | + | + | + | + | + {2:-- TERMINAL --} | + ]]) + end) + + it('will handle cterm and rgb attributes', function() + if helpers.pending_win32(pending) then return end + thelpers.set_fg(3) + thelpers.feed_data('text') + thelpers.feed_termcode('[38:2:255:128:0m') + thelpers.feed_data('color') + thelpers.clear_attrs() + thelpers.feed_data('text') + screen:expect{grid=[[ + tty ready | + {3:text}{4:color}text{1: } | + | + | + | + | + {2:-- TERMINAL --} | + ]]} + end) +end) + + describe(':terminal highlight with custom palette', function() local screen @@ -167,7 +214,7 @@ describe(':terminal highlight with custom palette', function() clear() screen = Screen.new(50, 7) screen:set_default_attr_ids({ - [1] = {foreground = tonumber('0x123456')}, + [1] = {foreground = tonumber('0x123456')}, -- no fg_indexed when overriden [2] = {foreground = 12}, [3] = {bold = true, reverse = true}, [5] = {background = 11}, diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index 060f065bfc..1df8df6f6e 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -449,7 +449,7 @@ describe("'scrollback' option", function() 38: line | 39: line | 40: line | - {IGNORE}| + {MATCH:.*}| {3:-- TERMINAL --} | ]]} end diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index bc83660c19..676d6ef76d 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -302,6 +302,49 @@ describe('TUI', function() expect_child_buf_lines({''}) end) + it('paste: select-mode', function() + feed_data('ithis is line 1\nthis is line 2\nline 3 is here\n\027') + wait_for_mode('n') + screen:expect{grid=[[ + this is line 1 | + this is line 2 | + line 3 is here | + {1: } | + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} + -- Select-mode. Use <C-n> to move down. + feed_data('gg04lgh\14\14') + wait_for_mode('s') + feed_data('\027[200~') + feed_data('just paste it™') + feed_data('\027[201~') + screen:expect{grid=[[ + thisjust paste it™{1:3} is here | + | + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} + -- Undo. + feed_data('u') + expect_child_buf_lines{ + 'this is line 1', + 'this is line 2', + 'line 3 is here', + '', + } + -- Redo. + feed_data('\18') -- <C-r> + expect_child_buf_lines{ + 'thisjust paste it™3 is here', + '', + } + end) + it('paste: terminal mode', function() feed_data(':set statusline=^^^^^^^\n') feed_data(':terminal '..nvim_dir..'/tty-test\n') @@ -535,7 +578,7 @@ describe('TUI', function() | {4:~ }| {5: }| - {8:paste: Error executing lua: vim.lua:197: Vim:E21: }| + {MATCH:paste: Error executing lua: vim.lua:%d+: Vim:E21: }| {8:Cannot make changes, 'modifiable' is off} | {10:Press ENTER or type command to continue}{1: } | {3:-- TERMINAL --} | @@ -680,11 +723,11 @@ describe('TUI', function() screen:set_option('rgb', true) screen:set_default_attr_ids({ [1] = {reverse = true}, - [2] = {foreground = tonumber('0x4040ff')}, + [2] = {foreground = tonumber('0x4040ff'), fg_indexed=true}, [3] = {bold = true, reverse = true}, [4] = {bold = true}, - [5] = {reverse = true, foreground = tonumber('0xe0e000')}, - [6] = {foreground = tonumber('0xe0e000')}, + [5] = {reverse = true, foreground = tonumber('0xe0e000'), fg_indexed=true}, + [6] = {foreground = tonumber('0xe0e000'), fg_indexed=true}, [7] = {reverse = true, foreground = Screen.colors.SeaGreen4}, [8] = {foreground = Screen.colors.SeaGreen4}, [9] = {bold = true, foreground = Screen.colors.Blue1}, @@ -728,6 +771,54 @@ describe('TUI', function() ]]) end) + it('forwards :term palette colors with termguicolors', function() + screen:set_rgb_cterm(true) + screen:set_default_attr_ids({ + [1] = {{reverse = true}, {reverse = true}}, + [2] = {{bold = true, reverse = true}, {bold = true, reverse = true}}, + [3] = {{bold = true}, {bold = true}}, + [4] = {{fg_indexed = true, foreground = tonumber('0xe0e000')}, {foreground = 3}}, + [5] = {{foreground = tonumber('0xff8000')}, {}}, + }) + + feed_data(':set statusline=^^^^^^^\n') + feed_data(':set termguicolors\n') + feed_data(':terminal '..nvim_dir..'/tty-test\n') + -- Depending on platform the above might or might not fit in the cmdline + -- so clear it for consistent behavior. + feed_data(':\027') + screen:expect{grid=[[ + {1:t}ty ready | + | + | + | + {2:^^^^^^^ }| + | + {3:-- TERMINAL --} | + ]]} + feed_data(':call chansend(&channel, "\\033[38;5;3mtext\\033[38:2:255:128:0mcolor\\033[0;10mtext")\n') + screen:expect{grid=[[ + {1:t}ty ready | + {4:text}{5:color}text | + | + | + {2:^^^^^^^ }| + | + {3:-- TERMINAL --} | + ]]} + + feed_data(':set notermguicolors\n') + screen:expect{grid=[[ + {1:t}ty ready | + {4:text}colortext | + | + | + {2:^^^^^^^ }| + :set notermguicolors | + {3:-- TERMINAL --} | + ]]} + end) + it('is included in nvim_list_uis()', function() feed_data(':echo map(nvim_list_uis(), {k,v -> sort(items(filter(v, {k,v -> k[:3] !=# "ext_" })))})\r') screen:expect([=[ diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua index 5df909f79c..65c5f67726 100644 --- a/test/functional/ui/bufhl_spec.lua +++ b/test/functional/ui/bufhl_spec.lua @@ -386,6 +386,22 @@ describe('Buffer highlighting', function() ]]) end) + it('can be retrieved', function() + local get_virtual_text = curbufmeths.get_virtual_text + local line_count = curbufmeths.line_count + + local s1 = {{'Köttbullar', 'Comment'}, {'Kräuterbutter'}} + local s2 = {{'こんにちは', 'Comment'}} + + set_virtual_text(-1, 0, s1, {}) + eq(s1, get_virtual_text(0)) + + set_virtual_text(-1, line_count(), s2, {}) + eq(s2, get_virtual_text(line_count())) + + eq({}, get_virtual_text(line_count() + 9000)) + end) + it('is not highlighted by visual selection', function() feed("ggVG") screen:expect([[ diff --git a/test/functional/ui/hlstate_spec.lua b/test/functional/ui/hlstate_spec.lua index 1e18df835a..2a567b28ee 100644 --- a/test/functional/ui/hlstate_spec.lua +++ b/test/functional/ui/hlstate_spec.lua @@ -181,11 +181,11 @@ describe('ext_hlstate detailed highlights', function() it("work with :terminal", function() screen:set_default_attr_ids({ [1] = {{}, {{hi_name = "TermCursorNC", ui_name = "TermCursorNC", kind = "ui"}}}, - [2] = {{foreground = 52479}, {{kind = "term"}}}, - [3] = {{bold = true, foreground = 52479}, {{kind = "term"}}}, - [4] = {{foreground = 52479}, {2, 1}}, - [5] = {{foreground = 4259839}, {{kind = "term"}}}, - [6] = {{foreground = 4259839}, {5, 1}}, + [2] = {{foreground = tonumber('0x00ccff'), fg_indexed=true}, {{kind = "term"}}}, + [3] = {{bold = true, foreground = tonumber('0x00ccff'), fg_indexed=true}, {{kind = "term"}}}, + [4] = {{foreground = tonumber('0x00ccff'), fg_indexed=true}, {2, 1}}, + [5] = {{foreground = tonumber('0x40ffff'), fg_indexed=true}, {{kind = "term"}}}, + [6] = {{foreground = tonumber('0x40ffff'), fg_indexed=true}, {5, 1}}, [7] = {{}, {{hi_name = "MsgArea", ui_name = "MsgArea", kind = "ui"}}}, }) command('enew | call termopen(["'..nvim_dir..'/tty-test"])') diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index d60cd08fb0..b841574643 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -88,14 +88,14 @@ local function common_setup(screen, inccommand, text) }) end - command("set inccommand=" .. (inccommand and inccommand or "")) + command("set inccommand=" .. (inccommand or "")) if text then insert(text) end end -describe(":substitute, inccommand=split", function() +describe(":substitute, inccommand=split interactivity", function() before_each(function() clear() common_setup(nil, "split", default_text) @@ -779,6 +779,59 @@ describe(":substitute, inccommand=split", function() {15:~ }| :silent tabedit %s/tw/to^ | ]]) + feed('<Esc>') + + -- leading colons + feed(':::%s/tw/to') + screen:expect{any=[[{12:to}o lines]]} + feed('<Esc>') + screen:expect{any=[[two lines]]} + end) + + it("ignores new-window modifiers when splitting the preview window", function() + -- one modifier + feed(':topleft %s/tw/to') + screen:expect([[ + Inc substitution on | + {12:to}o lines | + Inc substitution on | + {12:to}o lines | + | + {11:[No Name] [+] }| + |2| {12:to}o lines | + |4| {12:to}o lines | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :topleft %s/tw/to^ | + ]]) + feed('<Esc>') + screen:expect{any=[[two lines]]} + + -- multiple modifiers + feed(':topleft vert %s/tw/to') + screen:expect([[ + Inc substitution on | + {12:to}o lines | + Inc substitution on | + {12:to}o lines | + | + {11:[No Name] [+] }| + |2| {12:to}o lines | + |4| {12:to}o lines | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :topleft vert %s/tw/to^ | + ]]) + feed('<Esc>') + screen:expect{any=[[two lines]]} end) it('shows split window when typing the pattern', function() @@ -2529,6 +2582,49 @@ describe(":substitute", function() :%s/some\(thing\)\@!/every/^ | ]]) end) + + it("doesn't prompt to swap cmd range", function() + screen = Screen.new(50, 8) -- wide to avoid hit-enter prompt + common_setup(screen, "split", default_text) + feed(':2,1s/tw/MO/g') + + -- substitution preview should have been made, without prompting + screen:expect([[ + {12:MO}o lines | + {11:[No Name] [+] }| + |2| {12:MO}o lines | + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :2,1s/tw/MO/g^ | + ]]) + + -- but should be prompted on hitting enter + feed('<CR>') + screen:expect([[ + {12:MO}o lines | + {11:[No Name] [+] }| + |2| {12:MO}o lines | + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + {13:Backwards range given, OK to swap (y/n)?}^ | + ]]) + + feed('y') + screen:expect([[ + Inc substitution on | + ^MOo lines | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {13:Backwards range given, OK to swap (y/n)?}y | + ]]) + end) end) it(':substitute with inccommand during :terminal activity', function() diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index d16559bab2..40ea030f73 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -122,7 +122,7 @@ describe('ui/ext_messages', function() feed('G$x') screen:expect{grid=[[ line 1 | - {IGNORE}| + {MATCH:.*}| {1:~ }| {1:~ }| {1:~ }| @@ -747,7 +747,7 @@ describe('ui/ext_messages', function() {1:~ }| {1:~ }| ]], messages={{ - content = {{'E5105: Error while calling lua chunk: [string "<VimL compiled string>"]:1: such\nmultiline\nerror', 2}}, + content = {{'E5108: Error executing lua [string ":lua"]:1: such\nmultiline\nerror', 2}}, kind = "lua_error" }}} end) @@ -966,7 +966,7 @@ describe('ui/ext_messages', function() {1:~ }| {1:~ }| {1:~ }| - {IGNORE}| + {MATCH:.*}| {1:~ }| {1:~ }Nvim is open source and freely distributable{1: }| {1:~ }https://neovim.io/#chat{1: }| @@ -976,8 +976,8 @@ describe('ui/ext_messages', function() {1:~ }type :q{5:<Enter>} to exit {1: }| {1:~ }type :help{5:<Enter>} for help {1: }| {1:~ }| - {IGNORE}| - {IGNORE}| + {MATCH:.*}| + {MATCH:.*}| {1:~ }| {1:~ }| {1:~ }| @@ -1022,7 +1022,7 @@ describe('ui/ext_messages', function() | | | - {IGNORE}| + {MATCH:.*}| | Nvim is open source and freely distributable | https://neovim.io/#chat | @@ -1032,8 +1032,8 @@ describe('ui/ext_messages', function() type :q{5:<Enter>} to exit | type :help{5:<Enter>} for help | | - {IGNORE}| - {IGNORE}| + {MATCH:.*}| + {MATCH:.*}| | | | @@ -1146,97 +1146,96 @@ aliquip ex ea commodo consequat.]]) it('handles wrapped lines with line scroll', function() feed(':lua error(_G.x)<cr>') screen:expect{grid=[[ - {2:E5105: Error while calling lua chun}| - {2:k: [string "<VimL compiled string>"}| - {2:]:1: Lorem ipsum dolor sit amet, co}| - {2:nsectetur} | + {2:E5108: Error executing lua [string }| + {2:":lua"]:1: Lorem ipsum dolor sit am}| + {2:et, consectetur} | {2:adipisicing elit, sed do eiusmod te}| {2:mpor} | {2:incididunt ut labore et dolore magn}| + {2:a aliqua.} | {4:-- More --}^ | ]]} feed('j') screen:expect{grid=[[ - {2:k: [string "<VimL compiled string>"}| - {2:]:1: Lorem ipsum dolor sit amet, co}| - {2:nsectetur} | + {2:":lua"]:1: Lorem ipsum dolor sit am}| + {2:et, consectetur} | {2:adipisicing elit, sed do eiusmod te}| {2:mpor} | {2:incididunt ut labore et dolore magn}| {2:a aliqua.} | + {2:Ut enim ad minim veniam, quis nostr}| {4:-- More --}^ | ]]} feed('k') screen:expect{grid=[[ - {2:E5105: Error while calling lua chun}| - {2:k: [string "<VimL compiled string>"}| - {2:]:1: Lorem ipsum dolor sit amet, co}| - {2:nsectetur} | + {2:E5108: Error executing lua [string }| + {2:":lua"]:1: Lorem ipsum dolor sit am}| + {2:et, consectetur} | {2:adipisicing elit, sed do eiusmod te}| {2:mpor} | {2:incididunt ut labore et dolore magn}| + {2:a aliqua.} | {4:-- More --}^ | ]]} feed('j') screen:expect{grid=[[ - {2:k: [string "<VimL compiled string>"}| - {2:]:1: Lorem ipsum dolor sit amet, co}| - {2:nsectetur} | + {2:":lua"]:1: Lorem ipsum dolor sit am}| + {2:et, consectetur} | {2:adipisicing elit, sed do eiusmod te}| {2:mpor} | {2:incididunt ut labore et dolore magn}| {2:a aliqua.} | + {2:Ut enim ad minim veniam, quis nostr}| {4:-- More --}^ | ]]} - end) it('handles wrapped lines with page scroll', function() feed(':lua error(_G.x)<cr>') screen:expect{grid=[[ - {2:E5105: Error while calling lua chun}| - {2:k: [string "<VimL compiled string>"}| - {2:]:1: Lorem ipsum dolor sit amet, co}| - {2:nsectetur} | + {2:E5108: Error executing lua [string }| + {2:":lua"]:1: Lorem ipsum dolor sit am}| + {2:et, consectetur} | {2:adipisicing elit, sed do eiusmod te}| {2:mpor} | {2:incididunt ut labore et dolore magn}| + {2:a aliqua.} | {4:-- More --}^ | ]]} feed('d') screen:expect{grid=[[ - {2:adipisicing elit, sed do eiusmod te}| - {2:mpor} | {2:incididunt ut labore et dolore magn}| {2:a aliqua.} | {2:Ut enim ad minim veniam, quis nostr}| {2:ud xercitation} | {2:ullamco laboris nisi ut} | - {4:-- More --}^ | + {2:aliquip ex ea commodo consequat.} | + {4:Press ENTER or type command to cont}| + {4:inue}^ | ]]} feed('u') screen:expect{grid=[[ - {2:E5105: Error while calling lua chun}| - {2:k: [string "<VimL compiled string>"}| - {2:]:1: Lorem ipsum dolor sit amet, co}| - {2:nsectetur} | + {2:E5108: Error executing lua [string }| + {2:":lua"]:1: Lorem ipsum dolor sit am}| + {2:et, consectetur} | {2:adipisicing elit, sed do eiusmod te}| {2:mpor} | {2:incididunt ut labore et dolore magn}| + {2:a aliqua.} | {4:-- More --}^ | ]]} feed('d') screen:expect{grid=[[ - {2:adipisicing elit, sed do eiusmod te}| {2:mpor} | {2:incididunt ut labore et dolore magn}| {2:a aliqua.} | {2:Ut enim ad minim veniam, quis nostr}| {2:ud xercitation} | {2:ullamco laboris nisi ut} | + {2:aliquip ex ea commodo consequat.} | {4:-- More --}^ | ]]} end) @@ -1246,49 +1245,49 @@ aliquip ex ea commodo consequat.]]) feed(':lua error(_G.x)<cr>') screen:expect{grid=[[ - {3:E5105: Error while calling lua chun}| - {3:k: [string "<VimL compiled string>"}| - {3:]:1: Lorem ipsum dolor sit amet, co}| - {3:nsectetur}{5: }| + {3:E5108: Error executing lua [string }| + {3:":lua"]:1: Lorem ipsum dolor sit am}| + {3:et, consectetur}{5: }| {3:adipisicing elit, sed do eiusmod te}| {3:mpor}{5: }| {3:incididunt ut labore et dolore magn}| + {3:a aliqua.}{5: }| {6:-- More --}{5:^ }| ]]} feed('j') screen:expect{grid=[[ - {3:k: [string "<VimL compiled string>"}| - {3:]:1: Lorem ipsum dolor sit amet, co}| - {3:nsectetur}{5: }| + {3:":lua"]:1: Lorem ipsum dolor sit am}| + {3:et, consectetur}{5: }| {3:adipisicing elit, sed do eiusmod te}| {3:mpor}{5: }| {3:incididunt ut labore et dolore magn}| {3:a aliqua.}{5: }| + {3:Ut enim ad minim veniam, quis nostr}| {6:-- More --}{5:^ }| ]]} feed('k') screen:expect{grid=[[ - {3:E5105: Error while calling lua chun}| - {3:k: [string "<VimL compiled string>"}| - {3:]:1: Lorem ipsum dolor sit amet, co}| - {3:nsectetur}{5: }| + {3:E5108: Error executing lua [string }| + {3:":lua"]:1: Lorem ipsum dolor sit am}| + {3:et, consectetur}{5: }| {3:adipisicing elit, sed do eiusmod te}| {3:mpor}{5: }| {3:incididunt ut labore et dolore magn}| + {3:a aliqua.}{5: }| {6:-- More --}{5:^ }| ]]} feed('j') screen:expect{grid=[[ - {3:k: [string "<VimL compiled string>"}| - {3:]:1: Lorem ipsum dolor sit amet, co}| - {3:nsectetur}{5: }| + {3:":lua"]:1: Lorem ipsum dolor sit am}| + {3:et, consectetur}{5: }| {3:adipisicing elit, sed do eiusmod te}| {3:mpor}{5: }| {3:incididunt ut labore et dolore magn}| {3:a aliqua.}{5: }| + {3:Ut enim ad minim veniam, quis nostr}| {6:-- More --}{5:^ }| ]]} end) @@ -1297,46 +1296,46 @@ aliquip ex ea commodo consequat.]]) command("hi MsgArea guisp=Yellow") feed(':lua error(_G.x)<cr>') screen:expect{grid=[[ - {3:E5105: Error while calling lua chun}| - {3:k: [string "<VimL compiled string>"}| - {3:]:1: Lorem ipsum dolor sit amet, co}| - {3:nsectetur}{5: }| + {3:E5108: Error executing lua [string }| + {3:":lua"]:1: Lorem ipsum dolor sit am}| + {3:et, consectetur}{5: }| {3:adipisicing elit, sed do eiusmod te}| {3:mpor}{5: }| {3:incididunt ut labore et dolore magn}| + {3:a aliqua.}{5: }| {6:-- More --}{5:^ }| ]]} feed('d') screen:expect{grid=[[ - {3:adipisicing elit, sed do eiusmod te}| - {3:mpor}{5: }| {3:incididunt ut labore et dolore magn}| {3:a aliqua.}{5: }| {3:Ut enim ad minim veniam, quis nostr}| {3:ud xercitation}{5: }| {3:ullamco laboris nisi ut}{5: }| - {6:-- More --}{5:^ }| + {3:aliquip ex ea commodo consequat.}{5: }| + {6:Press ENTER or type command to cont}| + {6:inue}{5:^ }| ]]} feed('u') screen:expect{grid=[[ - {3:E5105: Error while calling lua chun}| - {3:k: [string "<VimL compiled string>"}| - {3:]:1: Lorem ipsum dolor sit amet, co}| - {3:nsectetur}{5: }| + {3:E5108: Error executing lua [string }| + {3:":lua"]:1: Lorem ipsum dolor sit am}| + {3:et, consectetur}{5: }| {3:adipisicing elit, sed do eiusmod te}| {3:mpor}{5: }| {3:incididunt ut labore et dolore magn}| + {3:a aliqua.}{5: }| {6:-- More --}{5:^ }| ]]} feed('d') screen:expect{grid=[[ - {3:adipisicing elit, sed do eiusmod te}| {3:mpor}{5: }| {3:incididunt ut labore et dolore magn}| {3:a aliqua.}{5: }| {3:Ut enim ad minim veniam, quis nostr}| {3:ud xercitation}{5: }| {3:ullamco laboris nisi ut}{5: }| + {3:aliquip ex ea commodo consequat.}{5: }| {6:-- More --}{5:^ }| ]]} end) @@ -1473,23 +1472,23 @@ aliquip ex ea commodo consequat.]]) it('can be resized', function() feed(':lua error(_G.x)<cr>') screen:expect{grid=[[ - {2:E5105: Error while calling lua chun}| - {2:k: [string "<VimL compiled string>"}| - {2:]:1: Lorem ipsum dolor sit amet, co}| - {2:nsectetur} | + {2:E5108: Error executing lua [string }| + {2:":lua"]:1: Lorem ipsum dolor sit am}| + {2:et, consectetur} | {2:adipisicing elit, sed do eiusmod te}| {2:mpor} | {2:incididunt ut labore et dolore magn}| + {2:a aliqua.} | {4:-- More --}^ | ]]} -- responds to resize, but text is not reflown screen:try_resize(45, 5) screen:expect{grid=[[ - {2:nsectetur} | {2:adipisicing elit, sed do eiusmod te} | {2:mpor} | {2:incididunt ut labore et dolore magn} | + {2:a aliqua.} | {4:-- More --}^ | ]]} @@ -1497,14 +1496,14 @@ aliquip ex ea commodo consequat.]]) -- text is not reflown; existing lines get cut screen:try_resize(30, 12) screen:expect{grid=[[ - {2:E5105: Error while calling lua}| - {2:k: [string "<VimL compiled str}| - {2:]:1: Lorem ipsum dolor sit ame}| - {2:nsectetur} | + {2:E5108: Error executing lua [st}| + {2:":lua"]:1: Lorem ipsum dolor s}| + {2:et, consectetur} | {2:adipisicing elit, sed do eiusm}| {2:mpore} | {2:incididunt ut labore et dolore}| - {2: magn} | + {2:a aliqua.} | + | | | | @@ -1515,18 +1514,18 @@ aliquip ex ea commodo consequat.]]) -- wrapped at the new screen size. feed('<cr>') screen:expect{grid=[[ - {2:k: [string "<VimL compiled str}| - {2:]:1: Lorem ipsum dolor sit ame}| - {2:nsectetur} | + {2:et, consectetur} | {2:adipisicing elit, sed do eiusm}| {2:mpore} | {2:incididunt ut labore et dolore}| - {2: magna aliqua.} | + {2:a aliqua.} | {2:Ut enim ad minim veniam, quis }| {2:nostrud xercitation} | {2:ullamco laboris nisi ut} | {2:aliquip ex ea commodo consequa}| - {4:-- More --}^ | + {2:t.} | + {4:Press ENTER or type command to}| + {4: continue}^ | ]]} feed('q') diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index b57e13fea1..d3f78bf77b 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -269,7 +269,7 @@ local ext_keys = { -- grid: Expected screen state (string). Each line represents a screen -- row. Last character of each row (typically "|") is stripped. -- Common indentation is stripped. --- Lines containing only "{IGNORE}|" are skipped. +-- "{MATCH:x}|" lines are matched against Lua pattern `x`. -- attr_ids: Expected text attributes. Screen rows are transformed according -- to this table, as follows: each substring S composed of -- characters having the same attributes will be substituted by @@ -390,9 +390,10 @@ function Screen:expect(expected, attr_ids, ...) err_msg = "Expected screen height " .. #expected_rows .. ' differs from actual height ' .. #actual_rows .. '.' end - for i = 1, #expected_rows do - msg_expected_rows[i] = expected_rows[i] - if expected_rows[i] ~= actual_rows[i] and expected_rows[i] ~= "{IGNORE}|" then + for i, row in ipairs(expected_rows) do + msg_expected_rows[i] = row + local m = (row ~= actual_rows[i] and row:match('{MATCH:(.*)}') or nil) + if row ~= actual_rows[i] and (not m or not actual_rows[i]:match(m)) then msg_expected_rows[i] = '*' .. msg_expected_rows[i] if i <= #actual_rows then actual_rows[i] = '*' .. actual_rows[i] @@ -1362,6 +1363,7 @@ function Screen:linegrid_check_attrs(attrs) if self._rgb_cterm then attr_rgb, attr_cterm, info = unpack(v) attr = {attr_rgb, attr_cterm} + info = info or {} elseif self._options.ext_hlstate then attr, info = unpack(v) else @@ -1400,11 +1402,12 @@ end function Screen:_pprint_hlitem(item) -- print(inspect(item)) local multi = self._rgb_cterm or self._options.ext_hlstate - local attrdict = "{"..self:_pprint_attrs(multi and item[1] or item).."}" + local cterm = (not self._rgb_cterm and not self._options.rgb) + local attrdict = "{"..self:_pprint_attrs(multi and item[1] or item, cterm).."}" local attrdict2, hlinfo local descdict = "" if self._rgb_cterm then - attrdict2 = ", {"..self:_pprint_attrs(item[2]).."}" + attrdict2 = ", {"..self:_pprint_attrs(item[2], true).."}" hlinfo = item[3] else attrdict2 = "" @@ -1433,13 +1436,15 @@ function Screen:_pprint_hlinfo(states) end -function Screen:_pprint_attrs(attrs) +function Screen:_pprint_attrs(attrs, cterm) local items = {} for f, v in pairs(attrs) do local desc = tostring(v) if f == "foreground" or f == "background" or f == "special" then if Screen.colornames[v] ~= nil then desc = "Screen.colors."..Screen.colornames[v] + elseif cterm then + desc = tostring(v) else desc = string.format("tonumber('0x%06x')",v) end @@ -1511,7 +1516,8 @@ function Screen:_equal_attrs(a, b) a.italic == b.italic and a.reverse == b.reverse and a.foreground == b.foreground and a.background == b.background and a.special == b.special and a.blend == b.blend and - a.strikethrough == b.strikethrough + a.strikethrough == b.strikethrough and + a.fg_indexed == b.fg_indexed and a.bg_indexed == b.bg_indexed end function Screen:_equal_info(a, b) diff --git a/test/helpers.lua b/test/helpers.lua index 4c526d217f..3f29a28c0d 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -74,7 +74,8 @@ function module.matches(pat, actual) error(string.format('Pattern does not match.\nPattern:\n%s\nActual:\n%s', pat, actual)) end --- Invokes `fn` and returns the error string, or raises an error if `fn` succeeds. +-- Invokes `fn` and returns the error string (may truncate full paths), or +-- raises an error if `fn` succeeds. -- -- Usage: -- -- Match exact string. @@ -88,7 +89,17 @@ function module.pcall_err(fn, ...) if status == true then error('expected failure, but got success') end + -- From this: + -- /home/foo/neovim/runtime/lua/vim/shared.lua:186: Expected string, got number + -- to this: + -- Expected string, got number local errmsg = tostring(rv):gsub('^[^:]+:%d+: ', '') + -- From this: + -- Error executing lua: /very/long/foo.lua:186: Expected string, got number + -- to this: + -- Error executing lua: .../foo.lua:186: Expected string, got number + errmsg = errmsg:gsub([[lua: [a-zA-Z]?:?[^:]-[/\]([^:/\]+):%d+: ]], 'lua: .../%1: ') + -- ^ Windows drive-letter (C:) return errmsg end |