incremental_sync_spec.lua 18 KB

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