neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

incremental_sync_spec.lua (18644B)


      1 -- Test suite for testing interactions with the incremental sync algorithms powering the LSP client
      2 local t = require('test.testutil')
      3 local n = require('test.functional.testnvim')()
      4 
      5 local api = n.api
      6 local clear = n.clear
      7 local eq = t.eq
      8 local exec_lua = n.exec_lua
      9 local feed = n.feed
     10 
     11 before_each(function()
     12  clear()
     13  exec_lua(function()
     14    local sync = require('vim.lsp.sync')
     15    local events = {}
     16 
     17    -- local format_line_ending = {
     18    --   ["unix"] = '\n',
     19    --   ["dos"] = '\r\n',
     20    --   ["mac"] = '\r',
     21    -- }
     22 
     23    -- local line_ending = format_line_ending[vim.api.nvim_get_option_value('fileformat', {})]
     24 
     25    --- @diagnostic disable-next-line:duplicate-set-field
     26    function _G.test_register(bufnr, id, position_encoding, line_ending)
     27      local prev_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, true)
     28 
     29      local function callback(_, bufnr0, _changedtick, firstline, lastline, new_lastline)
     30        if _G.test_unreg == id then
     31          return true
     32        end
     33 
     34        local curr_lines = vim.api.nvim_buf_get_lines(bufnr0, 0, -1, true)
     35        local incremental_change = sync.compute_diff(
     36          prev_lines,
     37          curr_lines,
     38          firstline,
     39          lastline,
     40          new_lastline,
     41          position_encoding,
     42          line_ending
     43        )
     44 
     45        table.insert(events, incremental_change)
     46        prev_lines = curr_lines
     47      end
     48      local opts = { on_lines = callback, on_detach = callback, on_reload = callback }
     49      vim.api.nvim_buf_attach(bufnr, false, opts)
     50    end
     51 
     52    --- @diagnostic disable-next-line:duplicate-set-field
     53    function _G.get_events()
     54      local ret_events = events
     55      events = {}
     56      return ret_events
     57    end
     58  end)
     59 end)
     60 
     61 --- @param edit_operations string[]
     62 local function test_edit(
     63  prev_buffer,
     64  edit_operations,
     65  expected_text_changes,
     66  position_encoding,
     67  line_ending
     68 )
     69  position_encoding = position_encoding or 'utf-16'
     70  line_ending = line_ending or '\n'
     71 
     72  api.nvim_buf_set_lines(0, 0, -1, true, prev_buffer)
     73  exec_lua(function()
     74    return _G.test_register(0, 'test1', position_encoding, line_ending)
     75  end)
     76 
     77  for _, edit in ipairs(edit_operations) do
     78    feed(edit)
     79  end
     80  eq(
     81    expected_text_changes,
     82    exec_lua(function()
     83      return _G.get_events()
     84    end)
     85  )
     86  exec_lua(function()
     87    _G.test_unreg = 'test1'
     88  end)
     89 end
     90 
     91 describe('incremental synchronization', function()
     92  describe('single line edit', function()
     93    it('inserting a character in an empty buffer', function()
     94      local expected_text_changes = {
     95        {
     96          range = {
     97            ['start'] = {
     98              character = 0,
     99              line = 0,
    100            },
    101            ['end'] = {
    102              character = 0,
    103              line = 0,
    104            },
    105          },
    106          rangeLength = 0,
    107          text = 'a',
    108        },
    109      }
    110      test_edit({ '' }, { 'ia' }, expected_text_changes, 'utf-16', '\n')
    111    end)
    112    it('inserting a character in the middle of a the first line', function()
    113      local expected_text_changes = {
    114        {
    115          range = {
    116            ['start'] = {
    117              character = 1,
    118              line = 0,
    119            },
    120            ['end'] = {
    121              character = 1,
    122              line = 0,
    123            },
    124          },
    125          rangeLength = 0,
    126          text = 'a',
    127        },
    128      }
    129      test_edit({ 'ab' }, { 'lia' }, expected_text_changes, 'utf-16', '\n')
    130    end)
    131    it('deleting the only character in a buffer', function()
    132      local expected_text_changes = {
    133        {
    134          range = {
    135            ['start'] = {
    136              character = 0,
    137              line = 0,
    138            },
    139            ['end'] = {
    140              character = 1,
    141              line = 0,
    142            },
    143          },
    144          rangeLength = 1,
    145          text = '',
    146        },
    147      }
    148      test_edit({ 'a' }, { 'x' }, expected_text_changes, 'utf-16', '\n')
    149    end)
    150    it('deleting a character in the middle of the line', function()
    151      local expected_text_changes = {
    152        {
    153          range = {
    154            ['start'] = {
    155              character = 1,
    156              line = 0,
    157            },
    158            ['end'] = {
    159              character = 2,
    160              line = 0,
    161            },
    162          },
    163          rangeLength = 1,
    164          text = '',
    165        },
    166      }
    167      test_edit({ 'abc' }, { 'lx' }, expected_text_changes, 'utf-16', '\n')
    168    end)
    169    it('replacing a character', function()
    170      local expected_text_changes = {
    171        {
    172          range = {
    173            ['start'] = {
    174              character = 0,
    175              line = 0,
    176            },
    177            ['end'] = {
    178              character = 1,
    179              line = 0,
    180            },
    181          },
    182          rangeLength = 1,
    183          text = 'b',
    184        },
    185      }
    186      test_edit({ 'a' }, { 'rb' }, expected_text_changes, 'utf-16', '\n')
    187    end)
    188    it('deleting the first line', function()
    189      local expected_text_changes = {
    190        {
    191          range = {
    192            ['start'] = {
    193              character = 0,
    194              line = 0,
    195            },
    196            ['end'] = {
    197              character = 0,
    198              line = 1,
    199            },
    200          },
    201          rangeLength = 6,
    202          text = '',
    203        },
    204      }
    205      test_edit({ 'hello', 'world' }, { 'ggdd' }, expected_text_changes, 'utf-16', '\n')
    206    end)
    207    it('deleting the last line', function()
    208      local expected_text_changes = {
    209        {
    210          range = {
    211            ['start'] = {
    212              character = 0,
    213              line = 1,
    214            },
    215            ['end'] = {
    216              character = 0,
    217              line = 2,
    218            },
    219          },
    220          rangeLength = 6,
    221          text = '',
    222        },
    223      }
    224      test_edit({ 'hello', 'world' }, { '2ggdd' }, expected_text_changes, 'utf-16', '\n')
    225    end)
    226    it('deleting all lines', function()
    227      local expected_text_changes = {
    228        {
    229          range = {
    230            ['start'] = {
    231              character = 0,
    232              line = 0,
    233            },
    234            ['end'] = {
    235              character = 5,
    236              line = 1,
    237            },
    238          },
    239          rangeLength = 11,
    240          text = '',
    241        },
    242      }
    243      test_edit({ 'hello', 'world' }, { 'ggdG' }, expected_text_changes, 'utf-16', '\n')
    244    end)
    245    it('deleting an empty line', function()
    246      local expected_text_changes = {
    247        {
    248          range = {
    249            ['start'] = {
    250              character = 0,
    251              line = 1,
    252            },
    253            ['end'] = {
    254              character = 0,
    255              line = 2,
    256            },
    257          },
    258          rangeLength = 1,
    259          text = '',
    260        },
    261      }
    262      test_edit({ 'hello world', '' }, { 'jdd' }, expected_text_changes, 'utf-16', '\n')
    263    end)
    264    it('adding a line', function()
    265      local expected_text_changes = {
    266        {
    267          range = {
    268            ['start'] = {
    269              character = 11,
    270              line = 0,
    271            },
    272            ['end'] = {
    273              character = 0,
    274              line = 1,
    275            },
    276          },
    277          rangeLength = 1,
    278          text = '\nhello world\n',
    279        },
    280      }
    281      test_edit({ 'hello world' }, { 'yyp' }, expected_text_changes, 'utf-16', '\n')
    282    end)
    283    it('adding an empty line', function()
    284      local expected_text_changes = {
    285        {
    286          range = {
    287            ['start'] = {
    288              character = 11,
    289              line = 0,
    290            },
    291            ['end'] = {
    292              character = 0,
    293              line = 1,
    294            },
    295          },
    296          rangeLength = 1,
    297          text = '\n\n',
    298        },
    299      }
    300      test_edit({ 'hello world' }, { 'o' }, expected_text_changes, 'utf-16', '\n')
    301    end)
    302    it('adding a line to an empty buffer', function()
    303      local expected_text_changes = {
    304        {
    305          range = {
    306            ['start'] = {
    307              character = 0,
    308              line = 0,
    309            },
    310            ['end'] = {
    311              character = 0,
    312              line = 1,
    313            },
    314          },
    315          rangeLength = 1,
    316          text = '\n\n',
    317        },
    318      }
    319      test_edit({ '' }, { 'o' }, expected_text_changes, 'utf-16', '\n')
    320    end)
    321    it('insert a line above the current line', function()
    322      local expected_text_changes = {
    323        {
    324          range = {
    325            ['start'] = {
    326              character = 0,
    327              line = 0,
    328            },
    329            ['end'] = {
    330              character = 0,
    331              line = 0,
    332            },
    333          },
    334          rangeLength = 0,
    335          text = '\n',
    336        },
    337      }
    338      test_edit({ '' }, { 'O' }, expected_text_changes, 'utf-16', '\n')
    339    end)
    340  end)
    341  describe('multi line edit', function()
    342    it('deletion and insertion', function()
    343      local expected_text_changes = {
    344        -- delete "_fsda" from end of line 1
    345        {
    346          range = {
    347            ['start'] = {
    348              character = 4,
    349              line = 1,
    350            },
    351            ['end'] = {
    352              character = 9,
    353              line = 1,
    354            },
    355          },
    356          rangeLength = 5,
    357          text = '',
    358        },
    359        -- delete "hello world\n" from line 2
    360        {
    361          range = {
    362            ['start'] = {
    363              character = 0,
    364              line = 2,
    365            },
    366            ['end'] = {
    367              character = 0,
    368              line = 3,
    369            },
    370          },
    371          rangeLength = 12,
    372          text = '',
    373        },
    374        -- delete "1234" from beginning of line 2
    375        {
    376          range = {
    377            ['start'] = {
    378              character = 0,
    379              line = 2,
    380            },
    381            ['end'] = {
    382              character = 4,
    383              line = 2,
    384            },
    385          },
    386          rangeLength = 4,
    387          text = '',
    388        },
    389        -- add " asdf" to end of line 1
    390        {
    391          range = {
    392            ['start'] = {
    393              character = 4,
    394              line = 1,
    395            },
    396            ['end'] = {
    397              character = 4,
    398              line = 1,
    399            },
    400          },
    401          rangeLength = 0,
    402          text = ' asdf',
    403        },
    404        -- delete " asdf\n" from line 2
    405        {
    406          range = {
    407            ['start'] = {
    408              character = 0,
    409              line = 2,
    410            },
    411            ['end'] = {
    412              character = 0,
    413              line = 3,
    414            },
    415          },
    416          rangeLength = 6,
    417          text = '',
    418        },
    419        -- undo entire deletion
    420        {
    421          range = {
    422            ['start'] = {
    423              character = 4,
    424              line = 1,
    425            },
    426            ['end'] = {
    427              character = 9,
    428              line = 1,
    429            },
    430          },
    431          rangeLength = 5,
    432          text = '_fdsa\nhello world\n1234 asdf',
    433        },
    434        -- redo entire deletion
    435        {
    436          range = {
    437            ['start'] = {
    438              character = 4,
    439              line = 1,
    440            },
    441            ['end'] = {
    442              character = 9,
    443              line = 3,
    444            },
    445          },
    446          rangeLength = 27,
    447          text = ' asdf',
    448        },
    449      }
    450      local original_lines = {
    451        '\\begin{document}',
    452        'test_fdsa',
    453        'hello world',
    454        '1234 asdf',
    455        '\\end{document}',
    456      }
    457      test_edit(original_lines, { 'jf_vejjbhhdu<C-R>' }, expected_text_changes, 'utf-16', '\n')
    458    end)
    459  end)
    460 
    461  describe('multi-operation edits', function()
    462    it('mult-line substitution', function()
    463      local expected_text_changes = {
    464        {
    465          range = {
    466            ['end'] = {
    467              character = 11,
    468              line = 2,
    469            },
    470            ['start'] = {
    471              character = 10,
    472              line = 2,
    473            },
    474          },
    475          rangeLength = 1,
    476          text = '',
    477        },
    478        {
    479          range = {
    480            ['end'] = {
    481              character = 10,
    482              line = 2,
    483            },
    484            start = {
    485              character = 10,
    486              line = 2,
    487            },
    488          },
    489          rangeLength = 0,
    490          text = '2',
    491        },
    492        {
    493          range = {
    494            ['end'] = {
    495              character = 11,
    496              line = 3,
    497            },
    498            ['start'] = {
    499              character = 10,
    500              line = 3,
    501            },
    502          },
    503          rangeLength = 1,
    504          text = '',
    505        },
    506        {
    507          range = {
    508            ['end'] = {
    509              character = 10,
    510              line = 3,
    511            },
    512            ['start'] = {
    513              character = 10,
    514              line = 3,
    515            },
    516          },
    517          rangeLength = 0,
    518          text = '3',
    519        },
    520        {
    521          range = {
    522            ['end'] = {
    523              character = 0,
    524              line = 3,
    525            },
    526            ['start'] = {
    527              character = 12,
    528              line = 2,
    529            },
    530          },
    531          rangeLength = 1,
    532          text = '\n',
    533        },
    534      }
    535      local original_lines = {
    536        '\\begin{document}',
    537        '\\section*{1}',
    538        '\\section*{1}',
    539        '\\section*{1}',
    540        '\\end{document}',
    541      }
    542      test_edit(original_lines, { '3gg$h<C-V>jg<C-A>' }, expected_text_changes, 'utf-16', '\n')
    543    end)
    544    it('join and undo', function()
    545      local expected_text_changes = {
    546        {
    547          range = {
    548            ['start'] = {
    549              character = 11,
    550              line = 0,
    551            },
    552            ['end'] = {
    553              character = 11,
    554              line = 0,
    555            },
    556          },
    557          rangeLength = 0,
    558          text = ' test3',
    559        },
    560        {
    561          range = {
    562            ['start'] = {
    563              character = 0,
    564              line = 1,
    565            },
    566            ['end'] = {
    567              character = 0,
    568              line = 2,
    569            },
    570          },
    571          rangeLength = 6,
    572          text = '',
    573        },
    574        {
    575          range = {
    576            ['start'] = {
    577              character = 11,
    578              line = 0,
    579            },
    580            ['end'] = {
    581              character = 17,
    582              line = 0,
    583            },
    584          },
    585          rangeLength = 6,
    586          text = '\ntest3',
    587        },
    588      }
    589      test_edit({ 'test1 test2', 'test3' }, { 'J', 'u' }, expected_text_changes, 'utf-16', '\n')
    590    end)
    591  end)
    592 
    593  describe('multi-byte edits', function()
    594    it('deleting a multibyte character', function()
    595      local expected_text_changes = {
    596        {
    597          range = {
    598            ['start'] = {
    599              character = 0,
    600              line = 0,
    601            },
    602            ['end'] = {
    603              character = 2,
    604              line = 0,
    605            },
    606          },
    607          rangeLength = 2,
    608          text = '',
    609        },
    610      }
    611      test_edit({ '🔥' }, { 'x' }, expected_text_changes, 'utf-16', '\n')
    612    end)
    613    it('replacing a multibyte character with matching prefix', function()
    614      local expected_text_changes = {
    615        {
    616          range = {
    617            ['start'] = {
    618              character = 0,
    619              line = 1,
    620            },
    621            ['end'] = {
    622              character = 1,
    623              line = 1,
    624            },
    625          },
    626          rangeLength = 1,
    627          text = '⟩',
    628        },
    629      }
    630      -- ⟨ is e29fa8, ⟩ is e29fa9
    631      local original_lines = {
    632        '\\begin{document}',
    633        '⟨',
    634        '\\end{document}',
    635      }
    636      test_edit(original_lines, { 'jr⟩' }, expected_text_changes, 'utf-16', '\n')
    637    end)
    638    it('replacing a multibyte character with matching suffix', function()
    639      local expected_text_changes = {
    640        {
    641          range = {
    642            ['start'] = {
    643              character = 0,
    644              line = 1,
    645            },
    646            ['end'] = {
    647              character = 1,
    648              line = 1,
    649            },
    650          },
    651          rangeLength = 1,
    652          text = 'ḟ',
    653        },
    654      }
    655      -- ฟ is e0b89f, ḟ is e1b89f
    656      local original_lines = {
    657        '\\begin{document}',
    658        'ฟ',
    659        '\\end{document}',
    660      }
    661      test_edit(original_lines, { 'jrḟ' }, expected_text_changes, 'utf-16', '\n')
    662    end)
    663    it('inserting before a multibyte character', function()
    664      local expected_text_changes = {
    665        {
    666          range = {
    667            ['start'] = {
    668              character = 0,
    669              line = 1,
    670            },
    671            ['end'] = {
    672              character = 0,
    673              line = 1,
    674            },
    675          },
    676          rangeLength = 0,
    677          text = ' ',
    678        },
    679      }
    680      local original_lines = {
    681        '\\begin{document}',
    682        '→',
    683        '\\end{document}',
    684      }
    685      test_edit(original_lines, { 'ji ' }, expected_text_changes, 'utf-16', '\n')
    686    end)
    687    it('deleting a multibyte character from a long line', function()
    688      local expected_text_changes = {
    689        {
    690          range = {
    691            ['start'] = {
    692              character = 85,
    693              line = 1,
    694            },
    695            ['end'] = {
    696              character = 86,
    697              line = 1,
    698            },
    699          },
    700          rangeLength = 1,
    701          text = '',
    702        },
    703      }
    704      local original_lines = {
    705        '\\begin{document}',
    706        '→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→',
    707        '\\end{document}',
    708      }
    709      test_edit(original_lines, { 'jx' }, expected_text_changes, 'utf-16', '\n')
    710    end)
    711    it('deleting multiple lines containing multibyte characters', function()
    712      local expected_text_changes = {
    713        {
    714          range = {
    715            ['start'] = {
    716              character = 0,
    717              line = 1,
    718            },
    719            ['end'] = {
    720              character = 0,
    721              line = 3,
    722            },
    723          },
    724          --utf 16 len of 🔥 is 2
    725          rangeLength = 8,
    726          text = '',
    727        },
    728      }
    729      test_edit(
    730        { 'a🔥', 'b🔥', 'c🔥', 'd🔥' },
    731        { 'j2dd' },
    732        expected_text_changes,
    733        'utf-16',
    734        '\n'
    735      )
    736    end)
    737  end)
    738 end)
    739 
    740 -- TODO(mjlbach): Add additional tests
    741 -- deleting single lone line
    742 -- 2 lines -> 2 line delete -> undo -> redo
    743 -- describe('future tests', function()
    744 --   -- This test is currently wrong, ask bjorn why dd on an empty line triggers on_lines
    745 --   it('deleting an empty line', function()
    746 --     local expected_text_changes = {{ }}
    747 --     test_edit({""}, {"ggdd"}, expected_text_changes, 'utf-16', '\n')
    748 --   end)
    749 -- end)