123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750 |
- -- Test suite for testing interactions with the incremental sync algorithms powering the LSP client
- local t = require('test.testutil')
- local n = require('test.functional.testnvim')()
- local api = n.api
- local clear = n.clear
- local eq = t.eq
- local exec_lua = n.exec_lua
- local feed = n.feed
- before_each(function()
- clear()
- exec_lua(function()
- local sync = require('vim.lsp.sync')
- local events = {}
- -- local format_line_ending = {
- -- ["unix"] = '\n',
- -- ["dos"] = '\r\n',
- -- ["mac"] = '\r',
- -- }
- -- local line_ending = format_line_ending[vim.api.nvim_get_option_value('fileformat', {})]
- --- @diagnostic disable-next-line:duplicate-set-field
- function _G.test_register(bufnr, id, position_encoding, line_ending)
- local prev_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, true)
- local function callback(_, bufnr0, _changedtick, firstline, lastline, new_lastline)
- if _G.test_unreg == id then
- return true
- end
- local curr_lines = vim.api.nvim_buf_get_lines(bufnr0, 0, -1, true)
- local incremental_change = sync.compute_diff(
- prev_lines,
- curr_lines,
- firstline,
- lastline,
- new_lastline,
- position_encoding,
- line_ending
- )
- table.insert(events, incremental_change)
- prev_lines = curr_lines
- end
- local opts = { on_lines = callback, on_detach = callback, on_reload = callback }
- vim.api.nvim_buf_attach(bufnr, false, opts)
- end
- --- @diagnostic disable-next-line:duplicate-set-field
- function _G.get_events()
- local ret_events = events
- events = {}
- return ret_events
- end
- end)
- end)
- --- @param edit_operations string[]
- local function test_edit(
- prev_buffer,
- edit_operations,
- expected_text_changes,
- position_encoding,
- line_ending
- )
- position_encoding = position_encoding or 'utf-16'
- line_ending = line_ending or '\n'
- api.nvim_buf_set_lines(0, 0, -1, true, prev_buffer)
- exec_lua(function()
- return _G.test_register(0, 'test1', position_encoding, line_ending)
- end)
- for _, edit in ipairs(edit_operations) do
- feed(edit)
- end
- eq(
- expected_text_changes,
- exec_lua(function()
- return _G.get_events()
- end)
- )
- exec_lua(function()
- _G.test_unreg = 'test1'
- end)
- end
- describe('incremental synchronization', function()
- describe('single line edit', function()
- it('inserting a character in an empty buffer', function()
- local expected_text_changes = {
- {
- range = {
- ['start'] = {
- character = 0,
- line = 0,
- },
- ['end'] = {
- character = 0,
- line = 0,
- },
- },
- rangeLength = 0,
- text = 'a',
- },
- }
- test_edit({ '' }, { 'ia' }, expected_text_changes, 'utf-16', '\n')
- end)
- it('inserting a character in the middle of a the first line', function()
- local expected_text_changes = {
- {
- range = {
- ['start'] = {
- character = 1,
- line = 0,
- },
- ['end'] = {
- character = 1,
- line = 0,
- },
- },
- rangeLength = 0,
- text = 'a',
- },
- }
- test_edit({ 'ab' }, { 'lia' }, expected_text_changes, 'utf-16', '\n')
- end)
- it('deleting the only character in a buffer', function()
- local expected_text_changes = {
- {
- range = {
- ['start'] = {
- character = 0,
- line = 0,
- },
- ['end'] = {
- character = 1,
- line = 0,
- },
- },
- rangeLength = 1,
- text = '',
- },
- }
- test_edit({ 'a' }, { 'x' }, expected_text_changes, 'utf-16', '\n')
- end)
- it('deleting a character in the middle of the line', function()
- local expected_text_changes = {
- {
- range = {
- ['start'] = {
- character = 1,
- line = 0,
- },
- ['end'] = {
- character = 2,
- line = 0,
- },
- },
- rangeLength = 1,
- text = '',
- },
- }
- test_edit({ 'abc' }, { 'lx' }, expected_text_changes, 'utf-16', '\n')
- end)
- it('replacing a character', function()
- local expected_text_changes = {
- {
- range = {
- ['start'] = {
- character = 0,
- line = 0,
- },
- ['end'] = {
- character = 1,
- line = 0,
- },
- },
- rangeLength = 1,
- text = 'b',
- },
- }
- test_edit({ 'a' }, { 'rb' }, expected_text_changes, 'utf-16', '\n')
- end)
- it('deleting the first line', function()
- local expected_text_changes = {
- {
- range = {
- ['start'] = {
- character = 0,
- line = 0,
- },
- ['end'] = {
- character = 0,
- line = 1,
- },
- },
- rangeLength = 6,
- text = '',
- },
- }
- test_edit({ 'hello', 'world' }, { 'ggdd' }, expected_text_changes, 'utf-16', '\n')
- end)
- it('deleting the last line', function()
- local expected_text_changes = {
- {
- range = {
- ['start'] = {
- character = 0,
- line = 1,
- },
- ['end'] = {
- character = 0,
- line = 2,
- },
- },
- rangeLength = 6,
- text = '',
- },
- }
- test_edit({ 'hello', 'world' }, { '2ggdd' }, expected_text_changes, 'utf-16', '\n')
- end)
- it('deleting all lines', function()
- local expected_text_changes = {
- {
- range = {
- ['start'] = {
- character = 0,
- line = 0,
- },
- ['end'] = {
- character = 5,
- line = 1,
- },
- },
- rangeLength = 11,
- text = '',
- },
- }
- test_edit({ 'hello', 'world' }, { 'ggdG' }, expected_text_changes, 'utf-16', '\n')
- end)
- it('deleting an empty line', function()
- local expected_text_changes = {
- {
- range = {
- ['start'] = {
- character = 0,
- line = 1,
- },
- ['end'] = {
- character = 0,
- line = 2,
- },
- },
- rangeLength = 1,
- text = '',
- },
- }
- test_edit({ 'hello world', '' }, { 'jdd' }, expected_text_changes, 'utf-16', '\n')
- end)
- it('adding a line', function()
- local expected_text_changes = {
- {
- range = {
- ['start'] = {
- character = 11,
- line = 0,
- },
- ['end'] = {
- character = 0,
- line = 1,
- },
- },
- rangeLength = 1,
- text = '\nhello world\n',
- },
- }
- test_edit({ 'hello world' }, { 'yyp' }, expected_text_changes, 'utf-16', '\n')
- end)
- it('adding an empty line', function()
- local expected_text_changes = {
- {
- range = {
- ['start'] = {
- character = 11,
- line = 0,
- },
- ['end'] = {
- character = 0,
- line = 1,
- },
- },
- rangeLength = 1,
- text = '\n\n',
- },
- }
- test_edit({ 'hello world' }, { 'o' }, expected_text_changes, 'utf-16', '\n')
- end)
- it('adding a line to an empty buffer', function()
- local expected_text_changes = {
- {
- range = {
- ['start'] = {
- character = 0,
- line = 0,
- },
- ['end'] = {
- character = 0,
- line = 1,
- },
- },
- rangeLength = 1,
- text = '\n\n',
- },
- }
- test_edit({ '' }, { 'o' }, expected_text_changes, 'utf-16', '\n')
- end)
- it('insert a line above the current line', function()
- local expected_text_changes = {
- {
- range = {
- ['start'] = {
- character = 0,
- line = 0,
- },
- ['end'] = {
- character = 0,
- line = 0,
- },
- },
- rangeLength = 0,
- text = '\n',
- },
- }
- test_edit({ '' }, { 'O' }, expected_text_changes, 'utf-16', '\n')
- end)
- end)
- describe('multi line edit', function()
- it('deletion and insertion', function()
- local expected_text_changes = {
- -- delete "_fsda" from end of line 1
- {
- range = {
- ['start'] = {
- character = 4,
- line = 1,
- },
- ['end'] = {
- character = 9,
- line = 1,
- },
- },
- rangeLength = 5,
- text = '',
- },
- -- delete "hello world\n" from line 2
- {
- range = {
- ['start'] = {
- character = 0,
- line = 2,
- },
- ['end'] = {
- character = 0,
- line = 3,
- },
- },
- rangeLength = 12,
- text = '',
- },
- -- delete "1234" from beginning of line 2
- {
- range = {
- ['start'] = {
- character = 0,
- line = 2,
- },
- ['end'] = {
- character = 4,
- line = 2,
- },
- },
- rangeLength = 4,
- text = '',
- },
- -- add " asdf" to end of line 1
- {
- range = {
- ['start'] = {
- character = 4,
- line = 1,
- },
- ['end'] = {
- character = 4,
- line = 1,
- },
- },
- rangeLength = 0,
- text = ' asdf',
- },
- -- delete " asdf\n" from line 2
- {
- range = {
- ['start'] = {
- character = 0,
- line = 2,
- },
- ['end'] = {
- character = 0,
- line = 3,
- },
- },
- rangeLength = 6,
- text = '',
- },
- -- undo entire deletion
- {
- range = {
- ['start'] = {
- character = 4,
- line = 1,
- },
- ['end'] = {
- character = 9,
- line = 1,
- },
- },
- rangeLength = 5,
- text = '_fdsa\nhello world\n1234 asdf',
- },
- -- redo entire deletion
- {
- range = {
- ['start'] = {
- character = 4,
- line = 1,
- },
- ['end'] = {
- character = 9,
- line = 3,
- },
- },
- rangeLength = 27,
- text = ' asdf',
- },
- }
- local original_lines = {
- '\\begin{document}',
- 'test_fdsa',
- 'hello world',
- '1234 asdf',
- '\\end{document}',
- }
- test_edit(original_lines, { 'jf_vejjbhhdu<C-R>' }, expected_text_changes, 'utf-16', '\n')
- end)
- end)
- describe('multi-operation edits', function()
- it('mult-line substitution', function()
- local expected_text_changes = {
- {
- range = {
- ['end'] = {
- character = 11,
- line = 2,
- },
- ['start'] = {
- character = 10,
- line = 2,
- },
- },
- rangeLength = 1,
- text = '',
- },
- {
- range = {
- ['end'] = {
- character = 10,
- line = 2,
- },
- start = {
- character = 10,
- line = 2,
- },
- },
- rangeLength = 0,
- text = '2',
- },
- {
- range = {
- ['end'] = {
- character = 11,
- line = 3,
- },
- ['start'] = {
- character = 10,
- line = 3,
- },
- },
- rangeLength = 1,
- text = '',
- },
- {
- range = {
- ['end'] = {
- character = 10,
- line = 3,
- },
- ['start'] = {
- character = 10,
- line = 3,
- },
- },
- rangeLength = 0,
- text = '3',
- },
- {
- range = {
- ['end'] = {
- character = 0,
- line = 3,
- },
- ['start'] = {
- character = 12,
- line = 2,
- },
- },
- rangeLength = 1,
- text = '\n',
- },
- }
- local original_lines = {
- '\\begin{document}',
- '\\section*{1}',
- '\\section*{1}',
- '\\section*{1}',
- '\\end{document}',
- }
- test_edit(original_lines, { '3gg$h<C-V>jg<C-A>' }, expected_text_changes, 'utf-16', '\n')
- end)
- it('join and undo', function()
- local expected_text_changes = {
- {
- range = {
- ['start'] = {
- character = 11,
- line = 0,
- },
- ['end'] = {
- character = 11,
- line = 0,
- },
- },
- rangeLength = 0,
- text = ' test3',
- },
- {
- range = {
- ['start'] = {
- character = 0,
- line = 1,
- },
- ['end'] = {
- character = 0,
- line = 2,
- },
- },
- rangeLength = 6,
- text = '',
- },
- {
- range = {
- ['start'] = {
- character = 11,
- line = 0,
- },
- ['end'] = {
- character = 17,
- line = 0,
- },
- },
- rangeLength = 6,
- text = '\ntest3',
- },
- }
- test_edit({ 'test1 test2', 'test3' }, { 'J', 'u' }, expected_text_changes, 'utf-16', '\n')
- end)
- end)
- describe('multi-byte edits', function()
- it('deleting a multibyte character', function()
- local expected_text_changes = {
- {
- range = {
- ['start'] = {
- character = 0,
- line = 0,
- },
- ['end'] = {
- character = 2,
- line = 0,
- },
- },
- rangeLength = 2,
- text = '',
- },
- }
- test_edit({ '🔥' }, { 'x' }, expected_text_changes, 'utf-16', '\n')
- end)
- it('replacing a multibyte character with matching prefix', function()
- local expected_text_changes = {
- {
- range = {
- ['start'] = {
- character = 0,
- line = 1,
- },
- ['end'] = {
- character = 1,
- line = 1,
- },
- },
- rangeLength = 1,
- text = '⟩',
- },
- }
- -- ⟨ is e29fa8, ⟩ is e29fa9
- local original_lines = {
- '\\begin{document}',
- '⟨',
- '\\end{document}',
- }
- test_edit(original_lines, { 'jr⟩' }, expected_text_changes, 'utf-16', '\n')
- end)
- it('replacing a multibyte character with matching suffix', function()
- local expected_text_changes = {
- {
- range = {
- ['start'] = {
- character = 0,
- line = 1,
- },
- ['end'] = {
- character = 1,
- line = 1,
- },
- },
- rangeLength = 1,
- text = 'ḟ',
- },
- }
- -- ฟ is e0b89f, ḟ is e1b89f
- local original_lines = {
- '\\begin{document}',
- 'ฟ',
- '\\end{document}',
- }
- test_edit(original_lines, { 'jrḟ' }, expected_text_changes, 'utf-16', '\n')
- end)
- it('inserting before a multibyte character', function()
- local expected_text_changes = {
- {
- range = {
- ['start'] = {
- character = 0,
- line = 1,
- },
- ['end'] = {
- character = 0,
- line = 1,
- },
- },
- rangeLength = 0,
- text = ' ',
- },
- }
- local original_lines = {
- '\\begin{document}',
- '→',
- '\\end{document}',
- }
- test_edit(original_lines, { 'ji ' }, expected_text_changes, 'utf-16', '\n')
- end)
- it('deleting a multibyte character from a long line', function()
- local expected_text_changes = {
- {
- range = {
- ['start'] = {
- character = 85,
- line = 1,
- },
- ['end'] = {
- character = 86,
- line = 1,
- },
- },
- rangeLength = 1,
- text = '',
- },
- }
- local original_lines = {
- '\\begin{document}',
- '→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→',
- '\\end{document}',
- }
- test_edit(original_lines, { 'jx' }, expected_text_changes, 'utf-16', '\n')
- end)
- it('deleting multiple lines containing multibyte characters', function()
- local expected_text_changes = {
- {
- range = {
- ['start'] = {
- character = 0,
- line = 1,
- },
- ['end'] = {
- character = 0,
- line = 3,
- },
- },
- --utf 16 len of 🔥 is 2
- rangeLength = 8,
- text = '',
- },
- }
- test_edit(
- { 'a🔥', 'b🔥', 'c🔥', 'd🔥' },
- { 'j2dd' },
- expected_text_changes,
- 'utf-16',
- '\n'
- )
- end)
- end)
- end)
- -- TODO(mjlbach): Add additional tests
- -- deleting single lone line
- -- 2 lines -> 2 line delete -> undo -> redo
- -- describe('future tests', function()
- -- -- This test is currently wrong, ask bjorn why dd on an empty line triggers on_lines
- -- it('deleting an empty line', function()
- -- local expected_text_changes = {{ }}
- -- test_edit({""}, {"ggdd"}, expected_text_changes, 'utf-16', '\n')
- -- end)
- -- end)
|