buffer_spec.lua 73 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185
  1. local t = require('test.testutil')
  2. local n = require('test.functional.testnvim')()
  3. local Screen = require('test.functional.ui.screen')
  4. local clear = n.clear
  5. local eq = t.eq
  6. local ok = t.ok
  7. local describe_lua_and_rpc = n.describe_lua_and_rpc(describe)
  8. local api = n.api
  9. local fn = n.fn
  10. local request = n.request
  11. local exc_exec = n.exc_exec
  12. local exec_lua = n.exec_lua
  13. local feed_command = n.feed_command
  14. local insert = n.insert
  15. local NIL = vim.NIL
  16. local command = n.command
  17. local feed = n.feed
  18. local pcall_err = t.pcall_err
  19. local assert_alive = n.assert_alive
  20. describe('api/buf', function()
  21. before_each(clear)
  22. -- access deprecated functions
  23. local function curbuf_depr(method, ...)
  24. return request('buffer_' .. method, 0, ...)
  25. end
  26. describe('nvim_buf_set_lines, nvim_buf_line_count', function()
  27. it('deprecated forms', function()
  28. eq(1, curbuf_depr('line_count'))
  29. curbuf_depr('insert', -1, { 'line' })
  30. eq(2, curbuf_depr('line_count'))
  31. curbuf_depr('insert', -1, { 'line' })
  32. eq(3, curbuf_depr('line_count'))
  33. curbuf_depr('del_line', -1)
  34. eq(2, curbuf_depr('line_count'))
  35. curbuf_depr('del_line', -1)
  36. curbuf_depr('del_line', -1)
  37. -- There's always at least one line
  38. eq(1, curbuf_depr('line_count'))
  39. end)
  40. it("doesn't crash just after set undolevels=1 #24894", function()
  41. local buf = api.nvim_create_buf(false, true)
  42. api.nvim_buf_set_option(buf, 'undolevels', -1)
  43. api.nvim_buf_set_lines(buf, 0, 1, false, {})
  44. assert_alive()
  45. end)
  46. it('cursor position is maintained after lines are inserted #9961', function()
  47. -- replace the buffer contents with these three lines.
  48. api.nvim_buf_set_lines(0, 0, -1, true, { 'line1', 'line2', 'line3', 'line4' })
  49. -- Set the current cursor to {3, 2}.
  50. api.nvim_win_set_cursor(0, { 3, 2 })
  51. -- add 2 lines and delete 1 line above the current cursor position.
  52. api.nvim_buf_set_lines(0, 1, 2, true, { 'line5', 'line6' })
  53. -- check the current set of lines in the buffer.
  54. eq({ 'line1', 'line5', 'line6', 'line3', 'line4' }, api.nvim_buf_get_lines(0, 0, -1, true))
  55. -- cursor should be moved below by 1 line.
  56. eq({ 4, 2 }, api.nvim_win_get_cursor(0))
  57. -- add a line after the current cursor position.
  58. api.nvim_buf_set_lines(0, 5, 5, true, { 'line7' })
  59. -- check the current set of lines in the buffer.
  60. eq(
  61. { 'line1', 'line5', 'line6', 'line3', 'line4', 'line7' },
  62. api.nvim_buf_get_lines(0, 0, -1, true)
  63. )
  64. -- cursor position is unchanged.
  65. eq({ 4, 2 }, api.nvim_win_get_cursor(0))
  66. -- overwrite current cursor line.
  67. api.nvim_buf_set_lines(0, 3, 5, true, { 'line8', 'line9' })
  68. -- check the current set of lines in the buffer.
  69. eq(
  70. { 'line1', 'line5', 'line6', 'line8', 'line9', 'line7' },
  71. api.nvim_buf_get_lines(0, 0, -1, true)
  72. )
  73. -- cursor position is unchanged.
  74. eq({ 4, 2 }, api.nvim_win_get_cursor(0))
  75. -- delete current cursor line.
  76. api.nvim_buf_set_lines(0, 3, 5, true, {})
  77. -- check the current set of lines in the buffer.
  78. eq({ 'line1', 'line5', 'line6', 'line7' }, api.nvim_buf_get_lines(0, 0, -1, true))
  79. -- cursor position is unchanged.
  80. eq({ 4, 2 }, api.nvim_win_get_cursor(0))
  81. end)
  82. it('cursor position is maintained in non-current window', function()
  83. api.nvim_buf_set_lines(0, 0, -1, true, { 'line1', 'line2', 'line3', 'line4' })
  84. api.nvim_win_set_cursor(0, { 3, 2 })
  85. local win = api.nvim_get_current_win()
  86. local buf = api.nvim_get_current_buf()
  87. command('new')
  88. api.nvim_buf_set_lines(buf, 1, 2, true, { 'line5', 'line6' })
  89. eq({ 'line1', 'line5', 'line6', 'line3', 'line4' }, api.nvim_buf_get_lines(buf, 0, -1, true))
  90. eq({ 4, 2 }, api.nvim_win_get_cursor(win))
  91. end)
  92. it('cursor position is maintained in TWO non-current windows', function()
  93. api.nvim_buf_set_lines(0, 0, -1, true, { 'line1', 'line2', 'line3', 'line4' })
  94. api.nvim_win_set_cursor(0, { 3, 2 })
  95. local win = api.nvim_get_current_win()
  96. local buf = api.nvim_get_current_buf()
  97. command('split')
  98. api.nvim_win_set_cursor(0, { 4, 2 })
  99. local win2 = api.nvim_get_current_win()
  100. -- set current window to third one with another buffer
  101. command('new')
  102. api.nvim_buf_set_lines(buf, 1, 2, true, { 'line5', 'line6' })
  103. eq({ 'line1', 'line5', 'line6', 'line3', 'line4' }, api.nvim_buf_get_lines(buf, 0, -1, true))
  104. eq({ 4, 2 }, api.nvim_win_get_cursor(win))
  105. eq({ 5, 2 }, api.nvim_win_get_cursor(win2))
  106. end)
  107. it('cursor position is maintained consistently with viewport', function()
  108. local screen = Screen.new(20, 12)
  109. local lines = { 'line1', 'line2', 'line3', 'line4', 'line5', 'line6' }
  110. local buf = api.nvim_get_current_buf()
  111. api.nvim_buf_set_lines(buf, 0, -1, true, lines)
  112. command('6')
  113. command('new')
  114. screen:expect {
  115. grid = [[
  116. ^ |
  117. {1:~ }|*4
  118. {3:[No Name] }|
  119. line5 |
  120. line6 |
  121. {1:~ }|*2
  122. {2:[No Name] [+] }|
  123. |
  124. ]],
  125. }
  126. lines[5] = 'boogalo 5'
  127. api.nvim_buf_set_lines(buf, 0, -1, true, lines)
  128. screen:expect {
  129. grid = [[
  130. ^ |
  131. {1:~ }|*4
  132. {3:[No Name] }|
  133. boogalo 5 |
  134. line6 |
  135. {1:~ }|*2
  136. {2:[No Name] [+] }|
  137. |
  138. ]],
  139. }
  140. command('wincmd w')
  141. screen:expect {
  142. grid = [[
  143. |
  144. {1:~ }|*4
  145. {2:[No Name] }|
  146. boogalo 5 |
  147. ^line6 |
  148. {1:~ }|*2
  149. {3:[No Name] [+] }|
  150. |
  151. ]],
  152. }
  153. end)
  154. it('line_count has defined behaviour for unloaded buffers', function()
  155. -- we'll need to know our bufnr for when it gets unloaded
  156. local bufnr = api.nvim_buf_get_number(0)
  157. -- replace the buffer contents with these three lines
  158. api.nvim_buf_set_lines(bufnr, 0, -1, true, { 'line1', 'line2', 'line3', 'line4' })
  159. -- check the line count is correct
  160. eq(4, api.nvim_buf_line_count(bufnr))
  161. -- force unload the buffer (this will discard changes)
  162. command('new')
  163. command('bunload! ' .. bufnr)
  164. -- line count for an unloaded buffer should always be 0
  165. eq(0, api.nvim_buf_line_count(bufnr))
  166. end)
  167. it('get_lines has defined behaviour for unloaded buffers', function()
  168. -- we'll need to know our bufnr for when it gets unloaded
  169. local bufnr = api.nvim_buf_get_number(0)
  170. -- replace the buffer contents with these three lines
  171. api.nvim_buf_set_lines(bufnr, 0, -1, true, { 'line1', 'line2', 'line3', 'line4' })
  172. -- confirm that getting lines works
  173. eq({ 'line2', 'line3' }, api.nvim_buf_get_lines(bufnr, 1, 3, true))
  174. -- force unload the buffer (this will discard changes)
  175. command('new')
  176. command('bunload! ' .. bufnr)
  177. -- attempting to get lines now always gives empty list
  178. eq({}, api.nvim_buf_get_lines(bufnr, 1, 3, true))
  179. -- it's impossible to get out-of-bounds errors for an unloaded buffer
  180. eq({}, api.nvim_buf_get_lines(bufnr, 8888, 9999, true))
  181. end)
  182. describe('handles topline', function()
  183. local screen
  184. before_each(function()
  185. screen = Screen.new(20, 12)
  186. api.nvim_buf_set_lines(
  187. 0,
  188. 0,
  189. -1,
  190. true,
  191. { 'aaa', 'bbb', 'ccc', 'ddd', 'www', 'xxx', 'yyy', 'zzz' }
  192. )
  193. api.nvim_set_option_value('modified', false, {})
  194. end)
  195. it('of current window', function()
  196. local win = api.nvim_get_current_win()
  197. local buf = api.nvim_get_current_buf()
  198. command('new | wincmd w')
  199. api.nvim_win_set_cursor(win, { 8, 0 })
  200. screen:expect {
  201. grid = [[
  202. |
  203. {1:~ }|*4
  204. {2:[No Name] }|
  205. www |
  206. xxx |
  207. yyy |
  208. ^zzz |
  209. {3:[No Name] }|
  210. |
  211. ]],
  212. }
  213. api.nvim_buf_set_lines(buf, 0, 2, true, { 'aaabbb' })
  214. screen:expect {
  215. grid = [[
  216. |
  217. {1:~ }|*4
  218. {2:[No Name] }|
  219. www |
  220. xxx |
  221. yyy |
  222. ^zzz |
  223. {3:[No Name] [+] }|
  224. |
  225. ]],
  226. }
  227. -- replacing topline keeps it the topline
  228. api.nvim_buf_set_lines(buf, 3, 4, true, { 'wwweeee' })
  229. screen:expect {
  230. grid = [[
  231. |
  232. {1:~ }|*4
  233. {2:[No Name] }|
  234. wwweeee |
  235. xxx |
  236. yyy |
  237. ^zzz |
  238. {3:[No Name] [+] }|
  239. |
  240. ]],
  241. }
  242. -- inserting just before topline does not scroll up if cursor would be moved
  243. api.nvim_buf_set_lines(buf, 3, 3, true, { 'mmm' })
  244. screen:expect {
  245. grid = [[
  246. |
  247. {1:~ }|*4
  248. {2:[No Name] }|
  249. wwweeee |
  250. xxx |
  251. yyy |
  252. ^zzz |
  253. {3:[No Name] [+] }|
  254. |
  255. ]],
  256. unchanged = true,
  257. }
  258. api.nvim_win_set_cursor(0, { 7, 0 })
  259. screen:expect {
  260. grid = [[
  261. |
  262. {1:~ }|*4
  263. {2:[No Name] }|
  264. wwweeee |
  265. xxx |
  266. ^yyy |
  267. zzz |
  268. {3:[No Name] [+] }|
  269. |
  270. ]],
  271. }
  272. api.nvim_buf_set_lines(buf, 4, 4, true, { 'mmmeeeee' })
  273. screen:expect {
  274. grid = [[
  275. |
  276. {1:~ }|*4
  277. {2:[No Name] }|
  278. mmmeeeee |
  279. wwweeee |
  280. xxx |
  281. ^yyy |
  282. {3:[No Name] [+] }|
  283. |
  284. ]],
  285. }
  286. end)
  287. it('of non-current window', function()
  288. local win = api.nvim_get_current_win()
  289. local buf = api.nvim_get_current_buf()
  290. command('new')
  291. api.nvim_win_set_cursor(win, { 8, 0 })
  292. screen:expect {
  293. grid = [[
  294. ^ |
  295. {1:~ }|*4
  296. {3:[No Name] }|
  297. www |
  298. xxx |
  299. yyy |
  300. zzz |
  301. {2:[No Name] }|
  302. |
  303. ]],
  304. }
  305. api.nvim_buf_set_lines(buf, 0, 2, true, { 'aaabbb' })
  306. screen:expect {
  307. grid = [[
  308. ^ |
  309. {1:~ }|*4
  310. {3:[No Name] }|
  311. www |
  312. xxx |
  313. yyy |
  314. zzz |
  315. {2:[No Name] [+] }|
  316. |
  317. ]],
  318. }
  319. -- replacing topline keeps it the topline
  320. api.nvim_buf_set_lines(buf, 3, 4, true, { 'wwweeee' })
  321. screen:expect {
  322. grid = [[
  323. ^ |
  324. {1:~ }|*4
  325. {3:[No Name] }|
  326. wwweeee |
  327. xxx |
  328. yyy |
  329. zzz |
  330. {2:[No Name] [+] }|
  331. |
  332. ]],
  333. }
  334. api.nvim_buf_set_lines(buf, 3, 3, true, { 'mmm' })
  335. screen:expect {
  336. grid = [[
  337. ^ |
  338. {1:~ }|*4
  339. {3:[No Name] }|
  340. wwweeee |
  341. xxx |
  342. yyy |
  343. zzz |
  344. {2:[No Name] [+] }|
  345. |
  346. ]],
  347. unchanged = true,
  348. }
  349. end)
  350. it('of split windows with same buffer', function()
  351. local win = api.nvim_get_current_win()
  352. local buf = api.nvim_get_current_buf()
  353. command('split')
  354. api.nvim_win_set_cursor(win, { 8, 0 })
  355. api.nvim_win_set_cursor(0, { 1, 0 })
  356. screen:expect {
  357. grid = [[
  358. ^aaa |
  359. bbb |
  360. ccc |
  361. ddd |
  362. www |
  363. {3:[No Name] }|
  364. www |
  365. xxx |
  366. yyy |
  367. zzz |
  368. {2:[No Name] }|
  369. |
  370. ]],
  371. }
  372. api.nvim_buf_set_lines(buf, 0, 2, true, { 'aaabbb' })
  373. screen:expect {
  374. grid = [[
  375. ^aaabbb |
  376. ccc |
  377. ddd |
  378. www |
  379. xxx |
  380. {3:[No Name] [+] }|
  381. www |
  382. xxx |
  383. yyy |
  384. zzz |
  385. {2:[No Name] [+] }|
  386. |
  387. ]],
  388. }
  389. -- replacing topline keeps it the topline
  390. api.nvim_buf_set_lines(buf, 3, 4, true, { 'wwweeee' })
  391. screen:expect {
  392. grid = [[
  393. ^aaabbb |
  394. ccc |
  395. ddd |
  396. wwweeee |
  397. xxx |
  398. {3:[No Name] [+] }|
  399. wwweeee |
  400. xxx |
  401. yyy |
  402. zzz |
  403. {2:[No Name] [+] }|
  404. |
  405. ]],
  406. }
  407. api.nvim_buf_set_lines(buf, 3, 3, true, { 'mmm' })
  408. screen:expect {
  409. grid = [[
  410. ^aaabbb |
  411. ccc |
  412. ddd |
  413. mmm |
  414. wwweeee |
  415. {3:[No Name] [+] }|
  416. wwweeee |
  417. xxx |
  418. yyy |
  419. zzz |
  420. {2:[No Name] [+] }|
  421. |
  422. ]],
  423. }
  424. end)
  425. end)
  426. it('handles clearing out non-current buffer #24911', function()
  427. local buf = api.nvim_get_current_buf()
  428. api.nvim_buf_set_lines(buf, 0, -1, true, { 'aaa', 'bbb', 'ccc' })
  429. command('new')
  430. api.nvim_buf_set_lines(0, 0, -1, true, { 'xxx', 'yyy', 'zzz' })
  431. api.nvim_buf_set_lines(buf, 0, -1, true, {})
  432. eq({ 'xxx', 'yyy', 'zzz' }, api.nvim_buf_get_lines(0, 0, -1, true))
  433. eq({ '' }, api.nvim_buf_get_lines(buf, 0, -1, true))
  434. end)
  435. end)
  436. describe('deprecated: {get,set,del}_line', function()
  437. it('works', function()
  438. eq('', curbuf_depr('get_line', 0))
  439. curbuf_depr('set_line', 0, 'line1')
  440. eq('line1', curbuf_depr('get_line', 0))
  441. curbuf_depr('set_line', 0, 'line2')
  442. eq('line2', curbuf_depr('get_line', 0))
  443. curbuf_depr('del_line', 0)
  444. eq('', curbuf_depr('get_line', 0))
  445. end)
  446. it('get_line: out-of-bounds is an error', function()
  447. curbuf_depr('set_line', 0, 'line1.a')
  448. eq(1, curbuf_depr('line_count')) -- sanity
  449. eq(false, pcall(curbuf_depr, 'get_line', 1))
  450. eq(false, pcall(curbuf_depr, 'get_line', -2))
  451. end)
  452. it('set_line, del_line: out-of-bounds is an error', function()
  453. curbuf_depr('set_line', 0, 'line1.a')
  454. eq(false, pcall(curbuf_depr, 'set_line', 1, 'line1.b'))
  455. eq(false, pcall(curbuf_depr, 'set_line', -2, 'line1.b'))
  456. eq(false, pcall(curbuf_depr, 'del_line', 2))
  457. eq(false, pcall(curbuf_depr, 'del_line', -3))
  458. end)
  459. it('can handle NULs', function()
  460. curbuf_depr('set_line', 0, 'ab\0cd')
  461. eq('ab\0cd', curbuf_depr('get_line', 0))
  462. end)
  463. end)
  464. describe('deprecated: {get,set}_line_slice', function()
  465. it('get_line_slice: out-of-bounds returns empty array', function()
  466. curbuf_depr('set_line_slice', 0, 0, true, true, { 'a', 'b', 'c' })
  467. eq({ 'a', 'b', 'c' }, curbuf_depr('get_line_slice', 0, 2, true, true)) --sanity
  468. eq({}, curbuf_depr('get_line_slice', 2, 3, false, true))
  469. eq({}, curbuf_depr('get_line_slice', 3, 9, true, true))
  470. eq({}, curbuf_depr('get_line_slice', 3, -1, true, true))
  471. eq({}, curbuf_depr('get_line_slice', -3, -4, false, true))
  472. eq({}, curbuf_depr('get_line_slice', -4, -5, true, true))
  473. end)
  474. it('set_line_slice: out-of-bounds extends past end', function()
  475. curbuf_depr('set_line_slice', 0, 0, true, true, { 'a', 'b', 'c' })
  476. eq({ 'a', 'b', 'c' }, curbuf_depr('get_line_slice', 0, 2, true, true)) --sanity
  477. eq({ 'c' }, curbuf_depr('get_line_slice', -1, 4, true, true))
  478. eq({ 'a', 'b', 'c' }, curbuf_depr('get_line_slice', 0, 5, true, true))
  479. curbuf_depr('set_line_slice', 4, 5, true, true, { 'd' })
  480. eq({ 'a', 'b', 'c', 'd' }, curbuf_depr('get_line_slice', 0, 5, true, true))
  481. curbuf_depr('set_line_slice', -4, -5, true, true, { 'e' })
  482. eq({ 'e', 'a', 'b', 'c', 'd' }, curbuf_depr('get_line_slice', 0, 5, true, true))
  483. end)
  484. it('works', function()
  485. eq({ '' }, curbuf_depr('get_line_slice', 0, -1, true, true))
  486. -- Replace buffer
  487. curbuf_depr('set_line_slice', 0, -1, true, true, { 'a', 'b', 'c' })
  488. eq({ 'a', 'b', 'c' }, curbuf_depr('get_line_slice', 0, -1, true, true))
  489. eq({ 'b', 'c' }, curbuf_depr('get_line_slice', 1, -1, true, true))
  490. eq({ 'b' }, curbuf_depr('get_line_slice', 1, 2, true, false))
  491. eq({}, curbuf_depr('get_line_slice', 1, 1, true, false))
  492. eq({ 'a', 'b' }, curbuf_depr('get_line_slice', 0, -1, true, false))
  493. eq({ 'b' }, curbuf_depr('get_line_slice', 1, -1, true, false))
  494. eq({ 'b', 'c' }, curbuf_depr('get_line_slice', -2, -1, true, true))
  495. curbuf_depr('set_line_slice', 1, 2, true, false, { 'a', 'b', 'c' })
  496. eq({ 'a', 'a', 'b', 'c', 'c' }, curbuf_depr('get_line_slice', 0, -1, true, true))
  497. curbuf_depr('set_line_slice', -1, -1, true, true, { 'a', 'b', 'c' })
  498. eq({ 'a', 'a', 'b', 'c', 'a', 'b', 'c' }, curbuf_depr('get_line_slice', 0, -1, true, true))
  499. curbuf_depr('set_line_slice', 0, -3, true, false, {})
  500. eq({ 'a', 'b', 'c' }, curbuf_depr('get_line_slice', 0, -1, true, true))
  501. curbuf_depr('set_line_slice', 0, -1, true, true, {})
  502. eq({ '' }, curbuf_depr('get_line_slice', 0, -1, true, true))
  503. end)
  504. end)
  505. describe_lua_and_rpc('nvim_buf_get_lines, nvim_buf_set_lines', function(lua_or_rpc)
  506. local function get_lines(...)
  507. return lua_or_rpc.nvim_buf_get_lines(0, ...)
  508. end
  509. local function set_lines(...)
  510. return lua_or_rpc.nvim_buf_set_lines(0, ...)
  511. end
  512. local function line_count()
  513. return lua_or_rpc.nvim_buf_line_count(0)
  514. end
  515. it('fails correctly when input is not valid', function()
  516. eq(1, lua_or_rpc.nvim_buf_get_number(0))
  517. eq(
  518. [['replacement string' item contains newlines]],
  519. pcall_err(lua_or_rpc.nvim_buf_set_lines, 1, 1, 2, false, { 'b\na' })
  520. )
  521. end)
  522. it("fails if 'nomodifiable'", function()
  523. command('set nomodifiable')
  524. eq(
  525. [[Buffer is not 'modifiable']],
  526. pcall_err(lua_or_rpc.nvim_buf_set_lines, 1, 1, 2, false, { 'a', 'b' })
  527. )
  528. end)
  529. it('has correct line_count when inserting and deleting', function()
  530. eq(1, line_count())
  531. set_lines(-1, -1, true, { 'line' })
  532. eq(2, line_count())
  533. set_lines(-1, -1, true, { 'line' })
  534. eq(3, line_count())
  535. set_lines(-2, -1, true, {})
  536. eq(2, line_count())
  537. set_lines(-2, -1, true, {})
  538. set_lines(-2, -1, true, {})
  539. -- There's always at least one line
  540. eq(1, line_count())
  541. end)
  542. it('can get, set and delete a single line', function()
  543. eq({ '' }, get_lines(0, 1, true))
  544. set_lines(0, 1, true, { 'line1' })
  545. eq({ 'line1' }, get_lines(0, 1, true))
  546. set_lines(0, 1, true, { 'line2' })
  547. eq({ 'line2' }, get_lines(0, 1, true))
  548. set_lines(0, 1, true, {})
  549. eq({ '' }, get_lines(0, 1, true))
  550. end)
  551. it('can get a single line with strict indexing', function()
  552. set_lines(0, 1, true, { 'line1.a' })
  553. eq(1, line_count()) -- sanity
  554. eq('Index out of bounds', pcall_err(get_lines, 1, 2, true))
  555. eq('Index out of bounds', pcall_err(get_lines, -3, -2, true))
  556. end)
  557. it('can get a single line with non-strict indexing', function()
  558. set_lines(0, 1, true, { 'line1.a' })
  559. eq(1, line_count()) -- sanity
  560. eq({}, get_lines(1, 2, false))
  561. eq({}, get_lines(-3, -2, false))
  562. end)
  563. it('can set and delete a single line with strict indexing', function()
  564. set_lines(0, 1, true, { 'line1.a' })
  565. eq('Index out of bounds', pcall_err(set_lines, 1, 2, true, { 'line1.b' }))
  566. eq('Index out of bounds', pcall_err(set_lines, -3, -2, true, { 'line1.c' }))
  567. eq({ 'line1.a' }, get_lines(0, -1, true))
  568. eq('Index out of bounds', pcall_err(set_lines, 1, 2, true, {}))
  569. eq('Index out of bounds', pcall_err(set_lines, -3, -2, true, {}))
  570. eq({ 'line1.a' }, get_lines(0, -1, true))
  571. end)
  572. it('can set and delete a single line with non-strict indexing', function()
  573. set_lines(0, 1, true, { 'line1.a' })
  574. set_lines(1, 2, false, { 'line1.b' })
  575. set_lines(-4, -3, false, { 'line1.c' })
  576. eq({ 'line1.c', 'line1.a', 'line1.b' }, get_lines(0, -1, true))
  577. set_lines(3, 4, false, {})
  578. set_lines(-5, -4, false, {})
  579. eq({ 'line1.c', 'line1.a', 'line1.b' }, get_lines(0, -1, true))
  580. end)
  581. it('can handle NULs', function()
  582. set_lines(0, 1, true, { 'ab\0cd' })
  583. eq({ 'ab\0cd' }, get_lines(0, -1, true))
  584. end)
  585. it('works with multiple lines', function()
  586. eq({ '' }, get_lines(0, -1, true))
  587. -- Replace buffer
  588. for _, mode in pairs({ false, true }) do
  589. set_lines(0, -1, mode, { 'a', 'b', 'c' })
  590. eq({ 'a', 'b', 'c' }, get_lines(0, -1, mode))
  591. eq({ 'b', 'c' }, get_lines(1, -1, mode))
  592. eq({ 'b' }, get_lines(1, 2, mode))
  593. eq({}, get_lines(1, 1, mode))
  594. eq({ 'a', 'b' }, get_lines(0, -2, mode))
  595. eq({ 'b' }, get_lines(1, -2, mode))
  596. eq({ 'b', 'c' }, get_lines(-3, -1, mode))
  597. set_lines(1, 2, mode, { 'a', 'b', 'c' })
  598. eq({ 'a', 'a', 'b', 'c', 'c' }, get_lines(0, -1, mode))
  599. set_lines(-2, -1, mode, { 'a', 'b', 'c' })
  600. eq({ 'a', 'a', 'b', 'c', 'a', 'b', 'c' }, get_lines(0, -1, mode))
  601. set_lines(0, -4, mode, {})
  602. eq({ 'a', 'b', 'c' }, get_lines(0, -1, mode))
  603. set_lines(0, -1, mode, {})
  604. eq({ '' }, get_lines(0, -1, mode))
  605. end
  606. end)
  607. it('can get line ranges with non-strict indexing', function()
  608. set_lines(0, -1, true, { 'a', 'b', 'c' })
  609. eq({ 'a', 'b', 'c' }, get_lines(0, -1, true)) --sanity
  610. eq({}, get_lines(3, 4, false))
  611. eq({}, get_lines(3, 10, false))
  612. eq({}, get_lines(-5, -5, false))
  613. eq({}, get_lines(3, -1, false))
  614. eq({}, get_lines(-3, -4, false))
  615. end)
  616. it('can get line ranges with strict indexing', function()
  617. set_lines(0, -1, true, { 'a', 'b', 'c' })
  618. eq({ 'a', 'b', 'c' }, get_lines(0, -1, true)) --sanity
  619. eq('Index out of bounds', pcall_err(get_lines, 3, 4, true))
  620. eq('Index out of bounds', pcall_err(get_lines, 3, 10, true))
  621. eq('Index out of bounds', pcall_err(get_lines, -5, -5, true))
  622. -- empty or inverted ranges are not errors
  623. eq({}, get_lines(3, -1, true))
  624. eq({}, get_lines(-3, -4, true))
  625. end)
  626. it('set_lines: out-of-bounds can extend past end', function()
  627. set_lines(0, -1, true, { 'a', 'b', 'c' })
  628. eq({ 'a', 'b', 'c' }, get_lines(0, -1, true)) --sanity
  629. eq({ 'c' }, get_lines(-2, 5, false))
  630. eq({ 'a', 'b', 'c' }, get_lines(0, 6, false))
  631. eq('Index out of bounds', pcall_err(set_lines, 4, 6, true, { 'd' }))
  632. set_lines(4, 6, false, { 'd' })
  633. eq({ 'a', 'b', 'c', 'd' }, get_lines(0, -1, true))
  634. eq('Index out of bounds', pcall_err(set_lines, -6, -6, true, { 'e' }))
  635. set_lines(-6, -6, false, { 'e' })
  636. eq({ 'e', 'a', 'b', 'c', 'd' }, get_lines(0, -1, true))
  637. end)
  638. it('set_lines on alternate buffer does not access invalid line (E315)', function()
  639. feed_command('set hidden')
  640. insert('Initial file')
  641. command('enew')
  642. insert([[
  643. More
  644. Lines
  645. Than
  646. In
  647. The
  648. Other
  649. Buffer]])
  650. feed_command('$')
  651. local retval = exc_exec("call nvim_buf_set_lines(1, 0, 1, v:false, ['test'])")
  652. eq(0, retval)
  653. end)
  654. it("set_lines of invisible buffer doesn't move cursor in current window", function()
  655. local screen = Screen.new(20, 5)
  656. insert([[
  657. Who would win?
  658. A real window
  659. with proper text]])
  660. local buf = lua_or_rpc.nvim_create_buf(false, true)
  661. screen:expect([[
  662. Who would win? |
  663. A real window |
  664. with proper tex^t |
  665. {1:~ }|
  666. |
  667. ]])
  668. lua_or_rpc.nvim_buf_set_lines(buf, 0, -1, true, { 'or some', 'scratchy text' })
  669. feed('i') -- provoke redraw
  670. screen:expect([[
  671. Who would win? |
  672. A real window |
  673. with proper tex^t |
  674. {1:~ }|
  675. {5:-- INSERT --} |
  676. ]])
  677. end)
  678. it('set_lines on hidden buffer preserves "previous window" #9741', function()
  679. insert([[
  680. visible buffer line 1
  681. line 2
  682. ]])
  683. local hiddenbuf = lua_or_rpc.nvim_create_buf(false, true)
  684. command('vsplit')
  685. command('vsplit')
  686. feed('<c-w>l<c-w>l<c-w>l')
  687. eq(3, fn.winnr())
  688. feed('<c-w>h')
  689. eq(2, fn.winnr())
  690. lua_or_rpc.nvim_buf_set_lines(hiddenbuf, 0, -1, true, { 'hidden buffer line 1', 'line 2' })
  691. feed('<c-w>p')
  692. eq(3, fn.winnr())
  693. end)
  694. it('set_lines on unloaded buffer #8659 #22670', function()
  695. local bufnr = api.nvim_get_current_buf()
  696. lua_or_rpc.nvim_buf_set_lines(bufnr, 0, -1, false, { 'a', 'b', 'c' })
  697. lua_or_rpc.nvim_buf_set_name(bufnr, 'set_lines')
  698. finally(function()
  699. os.remove('set_lines')
  700. end)
  701. command('write!')
  702. command('new')
  703. command('bunload! ' .. bufnr)
  704. local new_bufnr = fn.bufnr('set_lines', true)
  705. lua_or_rpc.nvim_buf_set_lines(new_bufnr, 0, -1, false, {})
  706. eq({ '' }, lua_or_rpc.nvim_buf_get_lines(new_bufnr, 0, -1, false))
  707. end)
  708. end)
  709. describe('nvim_buf_set_text', function()
  710. local function get_lines(...)
  711. return api.nvim_buf_get_lines(0, ...)
  712. end
  713. local function set_text(...)
  714. return api.nvim_buf_set_text(0, ...)
  715. end
  716. it('works', function()
  717. insert([[
  718. hello foo!
  719. text
  720. ]])
  721. eq({ 'hello foo!' }, get_lines(0, 1, true))
  722. -- can replace a single word
  723. set_text(0, 6, 0, 9, { 'world' })
  724. eq({ 'hello world!', 'text' }, get_lines(0, 2, true))
  725. -- can insert text
  726. set_text(0, 0, 0, 0, { 'well ' })
  727. eq({ 'well hello world!', 'text' }, get_lines(0, 2, true))
  728. -- can delete text
  729. set_text(0, 0, 0, 5, { '' })
  730. eq({ 'hello world!', 'text' }, get_lines(0, 2, true))
  731. -- can replace with multiple lines
  732. set_text(0, 6, 0, 11, { 'foo', 'wo', 'more' })
  733. eq({ 'hello foo', 'wo', 'more!', 'text' }, get_lines(0, 4, true))
  734. -- will join multiple lines if needed
  735. set_text(0, 6, 3, 4, { 'bar' })
  736. eq({ 'hello bar' }, get_lines(0, 1, true))
  737. -- can use negative line numbers
  738. set_text(-2, 0, -2, 5, { 'goodbye' })
  739. eq({ 'goodbye bar', '' }, get_lines(0, -1, true))
  740. set_text(-1, 0, -1, 0, { 'text' })
  741. eq({ 'goodbye bar', 'text' }, get_lines(0, 2, true))
  742. -- can append to a line
  743. set_text(1, 4, -1, 4, { ' and', 'more' })
  744. eq({ 'goodbye bar', 'text and', 'more' }, get_lines(0, 3, true))
  745. -- can use negative column numbers
  746. set_text(0, -5, 0, -1, { '!' })
  747. eq({ 'goodbye!' }, get_lines(0, 1, true))
  748. end)
  749. it('works with undo', function()
  750. insert([[
  751. hello world!
  752. foo bar
  753. ]])
  754. -- setting text
  755. set_text(0, 0, 0, 0, { 'well ' })
  756. feed('u')
  757. eq({ 'hello world!' }, get_lines(0, 1, true))
  758. -- deleting text
  759. set_text(0, 0, 0, 6, { '' })
  760. feed('u')
  761. eq({ 'hello world!' }, get_lines(0, 1, true))
  762. -- inserting newlines
  763. set_text(0, 0, 0, 0, { 'hello', 'mr ' })
  764. feed('u')
  765. eq({ 'hello world!' }, get_lines(0, 1, true))
  766. -- deleting newlines
  767. set_text(0, 0, 1, 4, { 'hello' })
  768. feed('u')
  769. eq({ 'hello world!' }, get_lines(0, 1, true))
  770. end)
  771. it('updates the cursor position', function()
  772. insert([[
  773. hello world!
  774. ]])
  775. -- position the cursor on `!`
  776. api.nvim_win_set_cursor(0, { 1, 11 })
  777. -- replace 'world' with 'foo'
  778. set_text(0, 6, 0, 11, { 'foo' })
  779. eq('hello foo!', curbuf_depr('get_line', 0))
  780. -- cursor should be moved left by two columns (replacement is shorter by 2 chars)
  781. eq({ 1, 9 }, api.nvim_win_get_cursor(0))
  782. end)
  783. it('updates the cursor position in non-current window', function()
  784. insert([[
  785. hello world!]])
  786. -- position the cursor on `!`
  787. api.nvim_win_set_cursor(0, { 1, 11 })
  788. local win = api.nvim_get_current_win()
  789. local buf = api.nvim_get_current_buf()
  790. command('new')
  791. -- replace 'world' with 'foo'
  792. api.nvim_buf_set_text(buf, 0, 6, 0, 11, { 'foo' })
  793. eq({ 'hello foo!' }, api.nvim_buf_get_lines(buf, 0, -1, true))
  794. -- cursor should be moved left by two columns (replacement is shorter by 2 chars)
  795. eq({ 1, 9 }, api.nvim_win_get_cursor(win))
  796. end)
  797. it('updates the cursor position in TWO non-current windows', function()
  798. insert([[
  799. hello world!]])
  800. -- position the cursor on `!`
  801. api.nvim_win_set_cursor(0, { 1, 11 })
  802. local win = api.nvim_get_current_win()
  803. local buf = api.nvim_get_current_buf()
  804. command('split')
  805. local win2 = api.nvim_get_current_win()
  806. -- position the cursor on `w`
  807. api.nvim_win_set_cursor(0, { 1, 6 })
  808. command('new')
  809. -- replace 'hello' with 'foo'
  810. api.nvim_buf_set_text(buf, 0, 0, 0, 5, { 'foo' })
  811. eq({ 'foo world!' }, api.nvim_buf_get_lines(buf, 0, -1, true))
  812. -- both cursors should be moved left by two columns (replacement is shorter by 2 chars)
  813. eq({ 1, 9 }, api.nvim_win_get_cursor(win))
  814. eq({ 1, 4 }, api.nvim_win_get_cursor(win2))
  815. end)
  816. describe('when text is being added right at cursor position #22526', function()
  817. it('updates the cursor position in NORMAL mode', function()
  818. insert([[
  819. abcd]])
  820. -- position the cursor on 'c'
  821. api.nvim_win_set_cursor(0, { 1, 2 })
  822. -- add 'xxx' before 'c'
  823. set_text(0, 2, 0, 2, { 'xxx' })
  824. eq({ 'abxxxcd' }, get_lines(0, -1, true))
  825. -- cursor should be on 'c'
  826. eq({ 1, 5 }, api.nvim_win_get_cursor(0))
  827. end)
  828. it('updates the cursor position only in non-current window when in INSERT mode', function()
  829. insert([[
  830. abcd]])
  831. -- position the cursor on 'c'
  832. api.nvim_win_set_cursor(0, { 1, 2 })
  833. -- open vertical split
  834. feed('<c-w>v')
  835. -- get into INSERT mode to treat cursor
  836. -- as being after 'b', not on 'c'
  837. feed('i')
  838. -- add 'xxx' between 'b' and 'c'
  839. set_text(0, 2, 0, 2, { 'xxx' })
  840. eq({ 'abxxxcd' }, get_lines(0, -1, true))
  841. -- in the current window cursor should stay after 'b'
  842. eq({ 1, 2 }, api.nvim_win_get_cursor(0))
  843. -- quit INSERT mode
  844. feed('<esc>')
  845. -- close current window
  846. feed('<c-w>c')
  847. -- in another window cursor should be on 'c'
  848. eq({ 1, 5 }, api.nvim_win_get_cursor(0))
  849. end)
  850. end)
  851. describe('when text is being deleted right at cursor position', function()
  852. it('leaves cursor at the same position in NORMAL mode', function()
  853. insert([[
  854. abcd]])
  855. -- position the cursor on 'b'
  856. api.nvim_win_set_cursor(0, { 1, 1 })
  857. -- delete 'b'
  858. set_text(0, 1, 0, 2, {})
  859. eq({ 'acd' }, get_lines(0, -1, true))
  860. -- cursor is now on 'c'
  861. eq({ 1, 1 }, api.nvim_win_get_cursor(0))
  862. end)
  863. it('maintains INSERT-mode cursor position current/non-current window', function()
  864. insert([[
  865. abcd]])
  866. -- position the cursor on 'b'
  867. api.nvim_win_set_cursor(0, { 1, 1 })
  868. -- open vertical split
  869. feed('<c-w>v')
  870. -- get into INSERT mode to treat cursor
  871. -- as being after 'a', not on 'b'
  872. feed('i')
  873. -- delete 'b'
  874. set_text(0, 1, 0, 2, {})
  875. eq({ 'acd' }, get_lines(0, -1, true))
  876. -- cursor in the current window should stay after 'a'
  877. eq({ 1, 1 }, api.nvim_win_get_cursor(0))
  878. -- quit INSERT mode
  879. feed('<esc>')
  880. -- close current window
  881. feed('<c-w>c')
  882. -- cursor in non-current window should stay on 'c'
  883. eq({ 1, 1 }, api.nvim_win_get_cursor(0))
  884. end)
  885. end)
  886. describe('when cursor is inside replaced row range', function()
  887. it('maintains cursor position if at start_row, but before start_col', function()
  888. insert([[
  889. This should be first
  890. then there is a line we do not want
  891. and finally the last one]])
  892. -- position the cursor on ' ' before 'first'
  893. api.nvim_win_set_cursor(0, { 1, 14 })
  894. set_text(0, 15, 2, 11, {
  895. 'the line we do not want',
  896. 'but hopefully',
  897. })
  898. eq({
  899. 'This should be the line we do not want',
  900. 'but hopefully the last one',
  901. }, get_lines(0, -1, true))
  902. -- cursor should stay at the same position
  903. eq({ 1, 14 }, api.nvim_win_get_cursor(0))
  904. end)
  905. it('maintains cursor position if at start_row and column is still valid', function()
  906. insert([[
  907. This should be first
  908. then there is a line we do not want
  909. and finally the last one]])
  910. -- position the cursor on 'f' in 'first'
  911. api.nvim_win_set_cursor(0, { 1, 15 })
  912. set_text(0, 15, 2, 11, {
  913. 'the line we do not want',
  914. 'but hopefully',
  915. })
  916. eq({
  917. 'This should be the line we do not want',
  918. 'but hopefully the last one',
  919. }, get_lines(0, -1, true))
  920. -- cursor should stay at the same position
  921. eq({ 1, 15 }, api.nvim_win_get_cursor(0))
  922. end)
  923. it('adjusts cursor column to keep it valid if start_row got smaller', function()
  924. insert([[
  925. This should be first
  926. then there is a line we do not want
  927. and finally the last one]])
  928. -- position the cursor on 't' in 'first'
  929. api.nvim_win_set_cursor(0, { 1, 19 })
  930. local cursor = exec_lua([[
  931. vim.api.nvim_buf_set_text(0, 0, 15, 2, 24, {'last'})
  932. return vim.api.nvim_win_get_cursor(0)
  933. ]])
  934. eq({ 'This should be last' }, get_lines(0, -1, true))
  935. -- cursor should end up on 't' in 'last'
  936. eq({ 1, 18 }, api.nvim_win_get_cursor(0))
  937. -- immediate call to nvim_win_get_cursor should have returned the same position
  938. eq({ 1, 18 }, cursor)
  939. end)
  940. it('adjusts cursor column to keep it valid if start_row decreased in INSERT mode', function()
  941. insert([[
  942. This should be first
  943. then there is a line we do not want
  944. and finally the last one]])
  945. -- position the cursor on 't' in 'first'
  946. api.nvim_win_set_cursor(0, { 1, 19 })
  947. -- enter INSERT mode to treat cursor as being after 't'
  948. feed('a')
  949. local cursor = exec_lua([[
  950. vim.api.nvim_buf_set_text(0, 0, 15, 2, 24, {'last'})
  951. return vim.api.nvim_win_get_cursor(0)
  952. ]])
  953. eq({ 'This should be last' }, get_lines(0, -1, true))
  954. -- cursor should end up after 't' in 'last'
  955. eq({ 1, 19 }, api.nvim_win_get_cursor(0))
  956. -- immediate call to nvim_win_get_cursor should have returned the same position
  957. eq({ 1, 19 }, cursor)
  958. end)
  959. it('adjusts cursor to valid column in row after start_row if it got smaller', function()
  960. insert([[
  961. This should be first
  962. then there is a line we do not want
  963. and finally the last one]])
  964. -- position the cursor on 'w' in 'want'
  965. api.nvim_win_set_cursor(0, { 2, 31 })
  966. local cursor = exec_lua([[
  967. vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
  968. '1',
  969. 'then 2',
  970. 'and then',
  971. })
  972. return vim.api.nvim_win_get_cursor(0)
  973. ]])
  974. eq({
  975. 'This should be 1',
  976. 'then 2',
  977. 'and then the last one',
  978. }, get_lines(0, -1, true))
  979. -- cursor column should end up at the end of a row
  980. eq({ 2, 5 }, api.nvim_win_get_cursor(0))
  981. -- immediate call to nvim_win_get_cursor should have returned the same position
  982. eq({ 2, 5 }, cursor)
  983. end)
  984. it(
  985. 'adjusts cursor to valid column in row after start_row if it got smaller in INSERT mode',
  986. function()
  987. insert([[
  988. This should be first
  989. then there is a line we do not want
  990. and finally the last one]])
  991. -- position the cursor on 'w' in 'want'
  992. api.nvim_win_set_cursor(0, { 2, 31 })
  993. -- enter INSERT mode
  994. feed('a')
  995. local cursor = exec_lua([[
  996. vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
  997. '1',
  998. 'then 2',
  999. 'and then',
  1000. })
  1001. return vim.api.nvim_win_get_cursor(0)
  1002. ]])
  1003. eq({
  1004. 'This should be 1',
  1005. 'then 2',
  1006. 'and then the last one',
  1007. }, get_lines(0, -1, true))
  1008. -- cursor column should end up at the end of a row
  1009. eq({ 2, 6 }, api.nvim_win_get_cursor(0))
  1010. -- immediate call to nvim_win_get_cursor should have returned the same position
  1011. eq({ 2, 6 }, cursor)
  1012. end
  1013. )
  1014. it('adjusts cursor line and column to keep it inside replacement range', function()
  1015. insert([[
  1016. This should be first
  1017. then there is a line we do not want
  1018. and finally the last one]])
  1019. -- position the cursor on 'n' in 'finally'
  1020. api.nvim_win_set_cursor(0, { 3, 6 })
  1021. local cursor = exec_lua([[
  1022. vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
  1023. 'the line we do not want',
  1024. 'but hopefully',
  1025. })
  1026. return vim.api.nvim_win_get_cursor(0)
  1027. ]])
  1028. eq({
  1029. 'This should be the line we do not want',
  1030. 'but hopefully the last one',
  1031. }, get_lines(0, -1, true))
  1032. -- cursor should end up on 'y' in 'hopefully'
  1033. -- to stay in the range, because it got smaller
  1034. eq({ 2, 12 }, api.nvim_win_get_cursor(0))
  1035. -- immediate call to nvim_win_get_cursor should have returned the same position
  1036. eq({ 2, 12 }, cursor)
  1037. end)
  1038. it('adjusts cursor line and column if replacement is empty', function()
  1039. insert([[
  1040. This should be first
  1041. then there is a line we do not want
  1042. and finally the last one]])
  1043. -- position the cursor on 'r' in 'there'
  1044. api.nvim_win_set_cursor(0, { 2, 8 })
  1045. local cursor = exec_lua([[
  1046. vim.api.nvim_buf_set_text(0, 0, 15, 2, 12, {})
  1047. return vim.api.nvim_win_get_cursor(0)
  1048. ]])
  1049. eq({ 'This should be the last one' }, get_lines(0, -1, true))
  1050. -- cursor should end up on the next column after deleted range
  1051. eq({ 1, 15 }, api.nvim_win_get_cursor(0))
  1052. -- immediate call to nvim_win_get_cursor should have returned the same position
  1053. eq({ 1, 15 }, cursor)
  1054. end)
  1055. it('adjusts cursor line and column if replacement is empty and start_col == 0', function()
  1056. insert([[
  1057. This should be first
  1058. then there is a line we do not want
  1059. and finally the last one]])
  1060. -- position the cursor on 'r' in 'there'
  1061. api.nvim_win_set_cursor(0, { 2, 8 })
  1062. local cursor = exec_lua([[
  1063. vim.api.nvim_buf_set_text(0, 0, 0, 2, 4, {})
  1064. return vim.api.nvim_win_get_cursor(0)
  1065. ]])
  1066. eq({ 'finally the last one' }, get_lines(0, -1, true))
  1067. -- cursor should end up in column 0
  1068. eq({ 1, 0 }, api.nvim_win_get_cursor(0))
  1069. -- immediate call to nvim_win_get_cursor should have returned the same position
  1070. eq({ 1, 0 }, cursor)
  1071. end)
  1072. it('adjusts cursor column if replacement ends at cursor row, after cursor column', function()
  1073. insert([[
  1074. This should be first
  1075. then there is a line we do not want
  1076. and finally the last one]])
  1077. -- position the cursor on 'y' in 'finally'
  1078. api.nvim_win_set_cursor(0, { 3, 10 })
  1079. set_text(0, 15, 2, 11, { '1', 'this 2', 'and then' })
  1080. eq({
  1081. 'This should be 1',
  1082. 'this 2',
  1083. 'and then the last one',
  1084. }, get_lines(0, -1, true))
  1085. -- cursor should end up on 'n' in 'then'
  1086. eq({ 3, 7 }, api.nvim_win_get_cursor(0))
  1087. end)
  1088. it(
  1089. 'adjusts cursor column if replacement ends at cursor row, at cursor column in INSERT mode',
  1090. function()
  1091. insert([[
  1092. This should be first
  1093. then there is a line we do not want
  1094. and finally the last one]])
  1095. -- position the cursor on 'y' at 'finally'
  1096. api.nvim_win_set_cursor(0, { 3, 10 })
  1097. -- enter INSERT mode to treat cursor as being between 'l' and 'y'
  1098. feed('i')
  1099. set_text(0, 15, 2, 11, { '1', 'this 2', 'and then' })
  1100. eq({
  1101. 'This should be 1',
  1102. 'this 2',
  1103. 'and then the last one',
  1104. }, get_lines(0, -1, true))
  1105. -- cursor should end up after 'n' in 'then'
  1106. eq({ 3, 8 }, api.nvim_win_get_cursor(0))
  1107. end
  1108. )
  1109. it('adjusts cursor column if replacement is inside of a single line', function()
  1110. insert([[
  1111. This should be first
  1112. then there is a line we do not want
  1113. and finally the last one]])
  1114. -- position the cursor on 'y' in 'finally'
  1115. api.nvim_win_set_cursor(0, { 3, 10 })
  1116. set_text(2, 4, 2, 11, { 'then' })
  1117. eq({
  1118. 'This should be first',
  1119. 'then there is a line we do not want',
  1120. 'and then the last one',
  1121. }, get_lines(0, -1, true))
  1122. -- cursor should end up on 'n' in 'then'
  1123. eq({ 3, 7 }, api.nvim_win_get_cursor(0))
  1124. end)
  1125. it('does not move cursor column after end of a line', function()
  1126. insert([[
  1127. This should be the only line here
  1128. !!!]])
  1129. -- position cursor on the last '1'
  1130. api.nvim_win_set_cursor(0, { 2, 2 })
  1131. local cursor = exec_lua([[
  1132. vim.api.nvim_buf_set_text(0, 0, 33, 1, 3, {})
  1133. return vim.api.nvim_win_get_cursor(0)
  1134. ]])
  1135. eq({ 'This should be the only line here' }, get_lines(0, -1, true))
  1136. -- cursor should end up on '!'
  1137. eq({ 1, 32 }, api.nvim_win_get_cursor(0))
  1138. -- immediate call to nvim_win_get_cursor should have returned the same position
  1139. eq({ 1, 32 }, cursor)
  1140. end)
  1141. it('does not move cursor column before start of a line', function()
  1142. insert('\n!!!')
  1143. -- position cursor on the last '1'
  1144. api.nvim_win_set_cursor(0, { 2, 2 })
  1145. local cursor = exec_lua([[
  1146. vim.api.nvim_buf_set_text(0, 0, 0, 1, 3, {})
  1147. return vim.api.nvim_win_get_cursor(0)
  1148. ]])
  1149. eq({ '' }, get_lines(0, -1, true))
  1150. -- cursor should end up on '!'
  1151. eq({ 1, 0 }, api.nvim_win_get_cursor(0))
  1152. -- immediate call to nvim_win_get_cursor should have returned the same position
  1153. eq({ 1, 0 }, cursor)
  1154. end)
  1155. describe('with virtualedit', function()
  1156. it('adjusts cursor line/col to keep inside replacement range if not after eol', function()
  1157. insert([[
  1158. This should be first
  1159. then there is a line we do not want
  1160. and finally the last one]])
  1161. -- position cursor on 't' in 'want'
  1162. api.nvim_win_set_cursor(0, { 2, 34 })
  1163. -- turn on virtualedit
  1164. command('set virtualedit=all')
  1165. local cursor = exec_lua([[
  1166. vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
  1167. 'the line we do not want',
  1168. 'but hopefully',
  1169. })
  1170. return vim.api.nvim_win_get_cursor(0)
  1171. ]])
  1172. eq({
  1173. 'This should be the line we do not want',
  1174. 'but hopefully the last one',
  1175. }, get_lines(0, -1, true))
  1176. -- cursor should end up on 'y' in 'hopefully'
  1177. -- to stay in the range
  1178. eq({ 2, 12 }, api.nvim_win_get_cursor(0))
  1179. -- immediate call to nvim_win_get_cursor should have returned the same position
  1180. eq({ 2, 12 }, cursor)
  1181. -- coladd should be 0
  1182. eq(0, fn.winsaveview().coladd)
  1183. end)
  1184. it('does not change cursor screen column when cursor >EOL and row got shorter', function()
  1185. insert([[
  1186. This should be first
  1187. then there is a line we do not want
  1188. and finally the last one]])
  1189. -- position cursor on 't' in 'want'
  1190. api.nvim_win_set_cursor(0, { 2, 34 })
  1191. -- turn on virtualedit
  1192. command('set virtualedit=all')
  1193. -- move cursor after eol
  1194. fn.winrestview({ coladd = 5 })
  1195. local cursor = exec_lua([[
  1196. vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
  1197. 'the line we do not want',
  1198. 'but hopefully',
  1199. })
  1200. return vim.api.nvim_win_get_cursor(0)
  1201. ]])
  1202. eq({
  1203. 'This should be the line we do not want',
  1204. 'but hopefully the last one',
  1205. }, get_lines(0, -1, true))
  1206. -- cursor should end up at eol of a new row
  1207. eq({ 2, 26 }, api.nvim_win_get_cursor(0))
  1208. -- immediate call to nvim_win_get_cursor should have returned the same position
  1209. eq({ 2, 26 }, cursor)
  1210. -- coladd should be increased so that cursor stays in the same screen column
  1211. eq(13, fn.winsaveview().coladd)
  1212. end)
  1213. it(
  1214. 'does not change cursor screen column when cursor is after eol and row got longer',
  1215. function()
  1216. insert([[
  1217. This should be first
  1218. then there is a line we do not want
  1219. and finally the last one]])
  1220. -- position cursor on 't' in 'first'
  1221. api.nvim_win_set_cursor(0, { 1, 19 })
  1222. -- turn on virtualedit
  1223. command('set virtualedit=all')
  1224. -- move cursor after eol
  1225. fn.winrestview({ coladd = 21 })
  1226. local cursor = exec_lua([[
  1227. vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
  1228. 'the line we do not want',
  1229. 'but hopefully',
  1230. })
  1231. return vim.api.nvim_win_get_cursor(0)
  1232. ]])
  1233. eq({
  1234. 'This should be the line we do not want',
  1235. 'but hopefully the last one',
  1236. }, get_lines(0, -1, true))
  1237. -- cursor should end up at eol of a new row
  1238. eq({ 1, 38 }, api.nvim_win_get_cursor(0))
  1239. -- immediate call to nvim_win_get_cursor should have returned the same position
  1240. eq({ 1, 38 }, cursor)
  1241. -- coladd should be increased so that cursor stays in the same screen column
  1242. eq(2, fn.winsaveview().coladd)
  1243. end
  1244. )
  1245. it(
  1246. 'does not change cursor screen column when cursor is after eol and row extended past cursor column',
  1247. function()
  1248. insert([[
  1249. This should be first
  1250. then there is a line we do not want
  1251. and finally the last one]])
  1252. -- position cursor on 't' in 'first'
  1253. api.nvim_win_set_cursor(0, { 1, 19 })
  1254. -- turn on virtualedit
  1255. command('set virtualedit=all')
  1256. -- move cursor after eol just a bit
  1257. fn.winrestview({ coladd = 3 })
  1258. local cursor = exec_lua([[
  1259. vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
  1260. 'the line we do not want',
  1261. 'but hopefully',
  1262. })
  1263. return vim.api.nvim_win_get_cursor(0)
  1264. ]])
  1265. eq({
  1266. 'This should be the line we do not want',
  1267. 'but hopefully the last one',
  1268. }, get_lines(0, -1, true))
  1269. -- cursor should stay at the same screen column
  1270. eq({ 1, 22 }, api.nvim_win_get_cursor(0))
  1271. -- immediate call to nvim_win_get_cursor should have returned the same position
  1272. eq({ 1, 22 }, cursor)
  1273. -- coladd should become 0
  1274. eq(0, fn.winsaveview().coladd)
  1275. end
  1276. )
  1277. it(
  1278. 'does not change cursor screen column when cursor is after eol and row range decreased',
  1279. function()
  1280. insert([[
  1281. This should be first
  1282. then there is a line we do not want
  1283. and one more
  1284. and finally the last one]])
  1285. -- position cursor on 'e' in 'more'
  1286. api.nvim_win_set_cursor(0, { 3, 11 })
  1287. -- turn on virtualedit
  1288. command('set virtualedit=all')
  1289. -- move cursor after eol
  1290. fn.winrestview({ coladd = 28 })
  1291. local cursor = exec_lua([[
  1292. vim.api.nvim_buf_set_text(0, 0, 15, 3, 11, {
  1293. 'the line we do not want',
  1294. 'but hopefully',
  1295. })
  1296. return vim.api.nvim_win_get_cursor(0)
  1297. ]])
  1298. eq({
  1299. 'This should be the line we do not want',
  1300. 'but hopefully the last one',
  1301. }, get_lines(0, -1, true))
  1302. -- cursor should end up at eol of a new row
  1303. eq({ 2, 26 }, api.nvim_win_get_cursor(0))
  1304. -- immediate call to nvim_win_get_cursor should have returned the same position
  1305. eq({ 2, 26 }, cursor)
  1306. -- coladd should be increased so that cursor stays in the same screen column
  1307. eq(13, fn.winsaveview().coladd)
  1308. end
  1309. )
  1310. end)
  1311. end)
  1312. describe('when cursor is at end_row and after end_col', function()
  1313. it('adjusts cursor column when only a newline is added or deleted', function()
  1314. insert([[
  1315. first line
  1316. second
  1317. line]])
  1318. -- position the cursor on 'i'
  1319. api.nvim_win_set_cursor(0, { 3, 2 })
  1320. set_text(1, 6, 2, 0, {})
  1321. eq({ 'first line', 'second line' }, get_lines(0, -1, true))
  1322. -- cursor should stay on 'i'
  1323. eq({ 2, 8 }, api.nvim_win_get_cursor(0))
  1324. -- add a newline back
  1325. set_text(1, 6, 1, 6, { '', '' })
  1326. eq({ 'first line', 'second', ' line' }, get_lines(0, -1, true))
  1327. -- cursor should return back to the original position
  1328. eq({ 3, 2 }, api.nvim_win_get_cursor(0))
  1329. end)
  1330. it(
  1331. 'adjusts cursor column if the range is not bound to either start or end of a line',
  1332. function()
  1333. insert([[
  1334. This should be first
  1335. then there is a line we do not want
  1336. and finally the last one]])
  1337. -- position the cursor on 'h' in 'the'
  1338. api.nvim_win_set_cursor(0, { 3, 13 })
  1339. set_text(0, 14, 2, 11, {})
  1340. eq({ 'This should be the last one' }, get_lines(0, -1, true))
  1341. -- cursor should stay on 'h'
  1342. eq({ 1, 16 }, api.nvim_win_get_cursor(0))
  1343. -- add deleted lines back
  1344. set_text(0, 14, 0, 14, {
  1345. ' first',
  1346. 'then there is a line we do not want',
  1347. 'and finally',
  1348. })
  1349. eq({
  1350. 'This should be first',
  1351. 'then there is a line we do not want',
  1352. 'and finally the last one',
  1353. }, get_lines(0, -1, true))
  1354. -- cursor should return back to the original position
  1355. eq({ 3, 13 }, api.nvim_win_get_cursor(0))
  1356. end
  1357. )
  1358. it(
  1359. 'adjusts cursor column if replacing lines in range, not just deleting and adding',
  1360. function()
  1361. insert([[
  1362. This should be first
  1363. then there is a line we do not want
  1364. and finally the last one]])
  1365. -- position the cursor on 's' in 'last'
  1366. api.nvim_win_set_cursor(0, { 3, 18 })
  1367. set_text(0, 15, 2, 11, {
  1368. 'the line we do not want',
  1369. 'but hopefully',
  1370. })
  1371. eq({
  1372. 'This should be the line we do not want',
  1373. 'but hopefully the last one',
  1374. }, get_lines(0, -1, true))
  1375. -- cursor should stay on 's'
  1376. eq({ 2, 20 }, api.nvim_win_get_cursor(0))
  1377. set_text(0, 15, 1, 13, {
  1378. 'first',
  1379. 'then there is a line we do not want',
  1380. 'and finally',
  1381. })
  1382. eq({
  1383. 'This should be first',
  1384. 'then there is a line we do not want',
  1385. 'and finally the last one',
  1386. }, get_lines(0, -1, true))
  1387. -- cursor should return back to the original position
  1388. eq({ 3, 18 }, api.nvim_win_get_cursor(0))
  1389. end
  1390. )
  1391. it('does not move cursor column after end of a line', function()
  1392. insert([[
  1393. This should be the only line here
  1394. ]])
  1395. -- position cursor at the empty line
  1396. api.nvim_win_set_cursor(0, { 2, 0 })
  1397. local cursor = exec_lua([[
  1398. vim.api.nvim_buf_set_text(0, 0, 33, 1, 0, {'!'})
  1399. return vim.api.nvim_win_get_cursor(0)
  1400. ]])
  1401. eq({ 'This should be the only line here!' }, get_lines(0, -1, true))
  1402. -- cursor should end up on '!'
  1403. eq({ 1, 33 }, api.nvim_win_get_cursor(0))
  1404. -- immediate call to nvim_win_get_cursor should have returned the same position
  1405. eq({ 1, 33 }, cursor)
  1406. end)
  1407. it('does not move cursor column before start of a line', function()
  1408. insert('\n')
  1409. eq({ '', '' }, get_lines(0, -1, true))
  1410. -- position cursor on the last '1'
  1411. api.nvim_win_set_cursor(0, { 2, 2 })
  1412. local cursor = exec_lua([[
  1413. vim.api.nvim_buf_set_text(0, 0, 0, 1, 0, {''})
  1414. return vim.api.nvim_win_get_cursor(0)
  1415. ]])
  1416. eq({ '' }, get_lines(0, -1, true))
  1417. -- cursor should end up on '!'
  1418. eq({ 1, 0 }, api.nvim_win_get_cursor(0))
  1419. -- immediate call to nvim_win_get_cursor should have returned the same position
  1420. eq({ 1, 0 }, cursor)
  1421. end)
  1422. end)
  1423. it('can handle NULs', function()
  1424. set_text(0, 0, 0, 0, { 'ab\0cd' })
  1425. eq('ab\0cd', curbuf_depr('get_line', 0))
  1426. end)
  1427. it('adjusts extmarks', function()
  1428. local ns = api.nvim_create_namespace('my-fancy-plugin')
  1429. insert([[
  1430. foo bar
  1431. baz
  1432. ]])
  1433. local id1 = api.nvim_buf_set_extmark(0, ns, 0, 1, {})
  1434. local id2 = api.nvim_buf_set_extmark(0, ns, 0, 7, {})
  1435. local id3 = api.nvim_buf_set_extmark(0, ns, 1, 1, {})
  1436. set_text(0, 4, 0, 7, { 'q' })
  1437. eq({ 'foo q', 'baz' }, get_lines(0, 2, true))
  1438. -- mark before replacement point is unaffected
  1439. eq({ 0, 1 }, api.nvim_buf_get_extmark_by_id(0, ns, id1, {}))
  1440. -- mark gets shifted back because the replacement was shorter
  1441. eq({ 0, 5 }, api.nvim_buf_get_extmark_by_id(0, ns, id2, {}))
  1442. -- mark on the next line is unaffected
  1443. eq({ 1, 1 }, api.nvim_buf_get_extmark_by_id(0, ns, id3, {}))
  1444. -- replacing the text spanning two lines will adjust the mark on the next line
  1445. set_text(0, 3, 1, 3, { 'qux' })
  1446. eq({ 'fooqux', '' }, get_lines(0, 2, true))
  1447. eq({ 0, 6 }, api.nvim_buf_get_extmark_by_id(0, ns, id3, {}))
  1448. -- but mark before replacement point is still unaffected
  1449. eq({ 0, 1 }, api.nvim_buf_get_extmark_by_id(0, ns, id1, {}))
  1450. -- and the mark in the middle was shifted to the end of the insertion
  1451. eq({ 0, 6 }, api.nvim_buf_get_extmark_by_id(0, ns, id2, {}))
  1452. -- marks should be put back into the same place after undoing
  1453. set_text(0, 0, 0, 2, { '' })
  1454. feed('u')
  1455. eq({ 0, 1 }, api.nvim_buf_get_extmark_by_id(0, ns, id1, {}))
  1456. eq({ 0, 6 }, api.nvim_buf_get_extmark_by_id(0, ns, id2, {}))
  1457. eq({ 0, 6 }, api.nvim_buf_get_extmark_by_id(0, ns, id3, {}))
  1458. -- marks should be shifted over by the correct number of bytes for multibyte
  1459. -- chars
  1460. set_text(0, 0, 0, 0, { 'Ø' })
  1461. eq({ 0, 3 }, api.nvim_buf_get_extmark_by_id(0, ns, id1, {}))
  1462. eq({ 0, 8 }, api.nvim_buf_get_extmark_by_id(0, ns, id2, {}))
  1463. eq({ 0, 8 }, api.nvim_buf_get_extmark_by_id(0, ns, id3, {}))
  1464. end)
  1465. it('correctly marks changed region for redraw #13890', function()
  1466. local screen = Screen.new(20, 5)
  1467. insert([[
  1468. AAA
  1469. BBB
  1470. ]])
  1471. api.nvim_buf_set_text(0, 0, 0, 1, 3, { 'XXX', 'YYY' })
  1472. screen:expect([[
  1473. XXX |
  1474. YYY |
  1475. ^ |
  1476. {1:~ }|
  1477. |
  1478. ]])
  1479. end)
  1480. it('errors on out-of-range', function()
  1481. insert([[
  1482. hello foo!
  1483. text]])
  1484. eq("Invalid 'start_row': out of range", pcall_err(set_text, 2, 0, 3, 0, {}))
  1485. eq("Invalid 'start_row': out of range", pcall_err(set_text, -3, 0, 0, 0, {}))
  1486. eq("Invalid 'end_row': out of range", pcall_err(set_text, 0, 0, 2, 0, {}))
  1487. eq("Invalid 'end_row': out of range", pcall_err(set_text, 0, 0, -3, 0, {}))
  1488. eq("Invalid 'start_col': out of range", pcall_err(set_text, 1, 5, 1, 5, {}))
  1489. eq("Invalid 'end_col': out of range", pcall_err(set_text, 1, 0, 1, 5, {}))
  1490. end)
  1491. it('errors when start is greater than end', function()
  1492. insert([[
  1493. hello foo!
  1494. text]])
  1495. eq("'start' is higher than 'end'", pcall_err(set_text, 1, 0, 0, 0, {}))
  1496. eq("'start' is higher than 'end'", pcall_err(set_text, 0, 1, 0, 0, {}))
  1497. end)
  1498. it('no heap-use-after-free when called consecutively #19643', function()
  1499. set_text(0, 0, 0, 0, { 'one', '', '', 'two' })
  1500. eq({ 'one', '', '', 'two' }, get_lines(0, 4, true))
  1501. api.nvim_win_set_cursor(0, { 1, 0 })
  1502. exec_lua([[
  1503. vim.api.nvim_buf_set_text(0, 0, 3, 1, 0, {''})
  1504. vim.api.nvim_buf_set_text(0, 0, 3, 1, 0, {''})
  1505. ]])
  1506. eq({ 'one', 'two' }, get_lines(0, 2, true))
  1507. end)
  1508. describe('handles topline', function()
  1509. local screen
  1510. before_each(function()
  1511. screen = Screen.new(20, 12)
  1512. api.nvim_buf_set_lines(
  1513. 0,
  1514. 0,
  1515. -1,
  1516. true,
  1517. { 'aaa', 'bbb', 'ccc', 'ddd', 'www', 'xxx', 'yyy', 'zzz' }
  1518. )
  1519. api.nvim_set_option_value('modified', false, {})
  1520. end)
  1521. it('of current window', function()
  1522. local win = api.nvim_get_current_win()
  1523. local buf = api.nvim_get_current_buf()
  1524. command('new | wincmd w')
  1525. api.nvim_win_set_cursor(win, { 8, 0 })
  1526. screen:expect {
  1527. grid = [[
  1528. |
  1529. {1:~ }|*4
  1530. {2:[No Name] }|
  1531. www |
  1532. xxx |
  1533. yyy |
  1534. ^zzz |
  1535. {3:[No Name] }|
  1536. |
  1537. ]],
  1538. }
  1539. api.nvim_buf_set_text(buf, 0, 3, 1, 0, { 'X' })
  1540. screen:expect {
  1541. grid = [[
  1542. |
  1543. {1:~ }|*4
  1544. {2:[No Name] }|
  1545. www |
  1546. xxx |
  1547. yyy |
  1548. ^zzz |
  1549. {3:[No Name] [+] }|
  1550. |
  1551. ]],
  1552. }
  1553. end)
  1554. it('of non-current window', function()
  1555. local win = api.nvim_get_current_win()
  1556. local buf = api.nvim_get_current_buf()
  1557. command('new')
  1558. api.nvim_win_set_cursor(win, { 8, 0 })
  1559. screen:expect {
  1560. grid = [[
  1561. ^ |
  1562. {1:~ }|*4
  1563. {3:[No Name] }|
  1564. www |
  1565. xxx |
  1566. yyy |
  1567. zzz |
  1568. {2:[No Name] }|
  1569. |
  1570. ]],
  1571. }
  1572. api.nvim_buf_set_text(buf, 0, 3, 1, 0, { 'X' })
  1573. screen:expect {
  1574. grid = [[
  1575. ^ |
  1576. {1:~ }|*4
  1577. {3:[No Name] }|
  1578. www |
  1579. xxx |
  1580. yyy |
  1581. zzz |
  1582. {2:[No Name] [+] }|
  1583. |
  1584. ]],
  1585. }
  1586. end)
  1587. it('of split windows with same buffer', function()
  1588. local win = api.nvim_get_current_win()
  1589. local buf = api.nvim_get_current_buf()
  1590. command('split')
  1591. api.nvim_win_set_cursor(win, { 8, 0 })
  1592. api.nvim_win_set_cursor(0, { 1, 1 })
  1593. screen:expect {
  1594. grid = [[
  1595. a^aa |
  1596. bbb |
  1597. ccc |
  1598. ddd |
  1599. www |
  1600. {3:[No Name] }|
  1601. www |
  1602. xxx |
  1603. yyy |
  1604. zzz |
  1605. {2:[No Name] }|
  1606. |
  1607. ]],
  1608. }
  1609. api.nvim_buf_set_text(buf, 0, 3, 1, 0, { 'X' })
  1610. screen:expect {
  1611. grid = [[
  1612. a^aaXbbb |
  1613. ccc |
  1614. ddd |
  1615. www |
  1616. xxx |
  1617. {3:[No Name] [+] }|
  1618. www |
  1619. xxx |
  1620. yyy |
  1621. zzz |
  1622. {2:[No Name] [+] }|
  1623. |
  1624. ]],
  1625. }
  1626. end)
  1627. end)
  1628. end)
  1629. describe_lua_and_rpc('nvim_buf_get_text', function(lua_or_rpc)
  1630. local get_text = lua_or_rpc.nvim_buf_get_text
  1631. before_each(function()
  1632. insert([[
  1633. hello foo!
  1634. text
  1635. more]])
  1636. end)
  1637. it('works', function()
  1638. eq({ 'hello' }, get_text(0, 0, 0, 0, 5, {}))
  1639. eq({ 'hello foo!' }, get_text(0, 0, 0, 0, 42, {}))
  1640. eq({ 'foo!' }, get_text(0, 0, 6, 0, 10, {}))
  1641. eq({ 'foo!', 'tex' }, get_text(0, 0, 6, 1, 3, {}))
  1642. eq({ 'foo!', 'tex' }, get_text(0, -3, 6, -2, 3, {}))
  1643. eq({ '' }, get_text(0, 0, 18, 0, 20, {}))
  1644. eq({ 'ext' }, get_text(0, -2, 1, -2, 4, {}))
  1645. eq({ 'hello foo!', 'text', 'm' }, get_text(0, 0, 0, 2, 1, {}))
  1646. eq({ 'hello foo!' }, get_text(0, 0, -987654321, 0, 987654321, {}))
  1647. eq({ '' }, get_text(0, 0, -15, 0, -20, {}))
  1648. end)
  1649. it('errors on out-of-range', function()
  1650. eq('Index out of bounds', pcall_err(get_text, 0, 2, 0, 4, 0, {}))
  1651. eq('Index out of bounds', pcall_err(get_text, 0, -4, 0, 0, 0, {}))
  1652. eq('Index out of bounds', pcall_err(get_text, 0, 0, 0, 3, 0, {}))
  1653. eq('Index out of bounds', pcall_err(get_text, 0, 0, 0, -4, 0, {}))
  1654. -- no ml_get errors should happen #19017
  1655. eq('', api.nvim_get_vvar('errmsg'))
  1656. end)
  1657. it('errors when start is greater than end', function()
  1658. eq("'start' is higher than 'end'", pcall_err(get_text, 0, 1, 0, 0, 0, {}))
  1659. eq('start_col must be less than or equal to end_col', pcall_err(get_text, 0, 0, 1, 0, 0, {}))
  1660. end)
  1661. end)
  1662. describe('nvim_buf_get_offset', function()
  1663. local get_offset = api.nvim_buf_get_offset
  1664. it('works', function()
  1665. api.nvim_buf_set_lines(0, 0, -1, true, { 'Some\r', 'exa\000mple', '', 'buf\rfer', 'text' })
  1666. eq(5, api.nvim_buf_line_count(0))
  1667. eq(0, get_offset(0, 0))
  1668. eq(6, get_offset(0, 1))
  1669. eq(15, get_offset(0, 2))
  1670. eq(16, get_offset(0, 3))
  1671. eq(24, get_offset(0, 4))
  1672. eq(29, get_offset(0, 5))
  1673. eq('Index out of bounds', pcall_err(get_offset, 0, 6))
  1674. eq('Index out of bounds', pcall_err(get_offset, 0, -1))
  1675. api.nvim_set_option_value('eol', false, {})
  1676. api.nvim_set_option_value('fixeol', false, {})
  1677. eq(28, get_offset(0, 5))
  1678. -- fileformat is ignored
  1679. api.nvim_set_option_value('fileformat', 'dos', {})
  1680. eq(0, get_offset(0, 0))
  1681. eq(6, get_offset(0, 1))
  1682. eq(15, get_offset(0, 2))
  1683. eq(16, get_offset(0, 3))
  1684. eq(24, get_offset(0, 4))
  1685. eq(28, get_offset(0, 5))
  1686. api.nvim_set_option_value('eol', true, {})
  1687. eq(29, get_offset(0, 5))
  1688. command('set hidden')
  1689. command('enew')
  1690. eq(6, api.nvim_buf_get_offset(1, 1))
  1691. command('bunload! 1')
  1692. eq(-1, api.nvim_buf_get_offset(1, 1))
  1693. eq(-1, api.nvim_buf_get_offset(1, 0))
  1694. end)
  1695. it('works in empty buffer', function()
  1696. eq(0, get_offset(0, 0))
  1697. eq(1, get_offset(0, 1))
  1698. eq(-1, fn.line2byte('$'))
  1699. end)
  1700. it('works in buffer with one line inserted', function()
  1701. feed('itext')
  1702. eq(0, get_offset(0, 0))
  1703. eq(5, get_offset(0, 1))
  1704. end)
  1705. end)
  1706. describe('nvim_buf_get_var, nvim_buf_set_var, nvim_buf_del_var', function()
  1707. it('works', function()
  1708. api.nvim_buf_set_var(0, 'lua', { 1, 2, { ['3'] = 1 } })
  1709. eq({ 1, 2, { ['3'] = 1 } }, api.nvim_buf_get_var(0, 'lua'))
  1710. eq({ 1, 2, { ['3'] = 1 } }, api.nvim_eval('b:lua'))
  1711. eq(1, fn.exists('b:lua'))
  1712. api.nvim_buf_del_var(0, 'lua')
  1713. eq(0, fn.exists('b:lua'))
  1714. eq('Key not found: lua', pcall_err(api.nvim_buf_del_var, 0, 'lua'))
  1715. api.nvim_buf_set_var(0, 'lua', 1)
  1716. command('lockvar b:lua')
  1717. eq('Key is locked: lua', pcall_err(api.nvim_buf_del_var, 0, 'lua'))
  1718. eq('Key is locked: lua', pcall_err(api.nvim_buf_set_var, 0, 'lua', 1))
  1719. eq('Key is read-only: changedtick', pcall_err(api.nvim_buf_del_var, 0, 'changedtick'))
  1720. eq('Key is read-only: changedtick', pcall_err(api.nvim_buf_set_var, 0, 'changedtick', 1))
  1721. end)
  1722. end)
  1723. describe('nvim_buf_get_changedtick', function()
  1724. it('works', function()
  1725. eq(2, api.nvim_buf_get_changedtick(0))
  1726. api.nvim_buf_set_lines(0, 0, 1, false, { 'abc\0', '\0def', 'ghi' })
  1727. eq(3, api.nvim_buf_get_changedtick(0))
  1728. eq(3, api.nvim_buf_get_var(0, 'changedtick'))
  1729. end)
  1730. it('buffer_set_var returns the old value', function()
  1731. local val1 = { 1, 2, { ['3'] = 1 } }
  1732. local val2 = { 4, 7 }
  1733. eq(NIL, request('buffer_set_var', 0, 'lua', val1))
  1734. eq(val1, request('buffer_set_var', 0, 'lua', val2))
  1735. end)
  1736. it('buffer_del_var returns the old value', function()
  1737. local val1 = { 1, 2, { ['3'] = 1 } }
  1738. local val2 = { 4, 7 }
  1739. eq(NIL, request('buffer_set_var', 0, 'lua', val1))
  1740. eq(val1, request('buffer_set_var', 0, 'lua', val2))
  1741. eq(val2, request('buffer_del_var', 0, 'lua'))
  1742. end)
  1743. end)
  1744. describe('nvim_get_option_value, nvim_set_option_value', function()
  1745. it('works', function()
  1746. eq(8, api.nvim_get_option_value('shiftwidth', {}))
  1747. api.nvim_set_option_value('shiftwidth', 4, {})
  1748. eq(4, api.nvim_get_option_value('shiftwidth', {}))
  1749. -- global-local option
  1750. api.nvim_set_option_value('define', 'test', { buf = 0 })
  1751. eq('test', api.nvim_get_option_value('define', { buf = 0 }))
  1752. -- Doesn't change the global value
  1753. eq('', api.nvim_get_option_value('define', { scope = 'global' }))
  1754. end)
  1755. it('returns values for unset local options', function()
  1756. -- 'undolevels' is only set to its "unset" value when a new buffer is
  1757. -- created
  1758. command('enew')
  1759. eq(-123456, api.nvim_get_option_value('undolevels', { buf = 0 }))
  1760. end)
  1761. end)
  1762. describe('nvim_buf_get_name, nvim_buf_set_name', function()
  1763. it('works', function()
  1764. command('new')
  1765. eq('', api.nvim_buf_get_name(0))
  1766. local new_name = api.nvim_eval('resolve(tempname())')
  1767. api.nvim_buf_set_name(0, new_name)
  1768. eq(new_name, api.nvim_buf_get_name(0))
  1769. command('w!')
  1770. eq(1, fn.filereadable(new_name))
  1771. os.remove(new_name)
  1772. end)
  1773. describe("with 'autochdir'", function()
  1774. local topdir
  1775. local oldbuf
  1776. local newbuf
  1777. before_each(function()
  1778. command('set shellslash')
  1779. topdir = fn.getcwd()
  1780. t.mkdir(topdir .. '/Xacd')
  1781. oldbuf = api.nvim_get_current_buf()
  1782. command('vnew')
  1783. newbuf = api.nvim_get_current_buf()
  1784. command('set autochdir')
  1785. end)
  1786. after_each(function()
  1787. n.rmdir(topdir .. '/Xacd')
  1788. end)
  1789. it('does not change cwd with non-current buffer', function()
  1790. api.nvim_buf_set_name(oldbuf, topdir .. '/Xacd/foo.txt')
  1791. eq(topdir, fn.getcwd())
  1792. end)
  1793. it('changes cwd with current buffer', function()
  1794. api.nvim_buf_set_name(newbuf, topdir .. '/Xacd/foo.txt')
  1795. eq(topdir .. '/Xacd', fn.getcwd())
  1796. end)
  1797. end)
  1798. end)
  1799. describe('nvim_buf_is_loaded', function()
  1800. it('works', function()
  1801. -- record our buffer number for when we unload it
  1802. local bufnr = api.nvim_buf_get_number(0)
  1803. -- api should report that the buffer is loaded
  1804. ok(api.nvim_buf_is_loaded(bufnr))
  1805. -- hide the current buffer by switching to a new empty buffer
  1806. -- Careful! we need to modify the buffer first or vim will just reuse it
  1807. api.nvim_buf_set_lines(bufnr, 0, -1, true, { 'line1' })
  1808. command('hide enew')
  1809. -- confirm the buffer is hidden, but still loaded
  1810. local infolist = api.nvim_eval('getbufinfo(' .. bufnr .. ')')
  1811. eq(1, #infolist)
  1812. eq(1, infolist[1].hidden)
  1813. eq(1, infolist[1].loaded)
  1814. -- now force unload the buffer
  1815. command('bunload! ' .. bufnr)
  1816. -- confirm the buffer is unloaded
  1817. infolist = api.nvim_eval('getbufinfo(' .. bufnr .. ')')
  1818. eq(0, infolist[1].loaded)
  1819. -- nvim_buf_is_loaded() should also report the buffer as unloaded
  1820. eq(false, api.nvim_buf_is_loaded(bufnr))
  1821. end)
  1822. end)
  1823. describe('nvim_buf_is_valid', function()
  1824. it('works', function()
  1825. command('new')
  1826. local b = api.nvim_get_current_buf()
  1827. ok(api.nvim_buf_is_valid(b))
  1828. command('bw!')
  1829. ok(not api.nvim_buf_is_valid(b))
  1830. end)
  1831. end)
  1832. describe('nvim_buf_delete', function()
  1833. it('allows for just deleting', function()
  1834. command('new')
  1835. local b = api.nvim_get_current_buf()
  1836. ok(api.nvim_buf_is_valid(b))
  1837. api.nvim_buf_delete(b, {})
  1838. ok(not api.nvim_buf_is_loaded(b))
  1839. ok(not api.nvim_buf_is_valid(b))
  1840. end)
  1841. it('allows for just unloading', function()
  1842. command('new')
  1843. local b = api.nvim_get_current_buf()
  1844. ok(api.nvim_buf_is_valid(b))
  1845. api.nvim_buf_delete(b, { unload = true })
  1846. ok(not api.nvim_buf_is_loaded(b))
  1847. ok(api.nvim_buf_is_valid(b))
  1848. end)
  1849. end)
  1850. describe('nvim_buf_get_mark', function()
  1851. it('works', function()
  1852. api.nvim_buf_set_lines(0, -1, -1, true, { 'a', 'bit of', 'text' })
  1853. api.nvim_win_set_cursor(0, { 3, 4 })
  1854. command('mark v')
  1855. eq({ 3, 0 }, api.nvim_buf_get_mark(0, 'v'))
  1856. end)
  1857. end)
  1858. describe('nvim_buf_set_mark', function()
  1859. it('works with buffer local marks', function()
  1860. api.nvim_buf_set_lines(0, -1, -1, true, { 'a', 'bit of', 'text' })
  1861. eq(true, api.nvim_buf_set_mark(0, 'z', 1, 1, {}))
  1862. eq({ 1, 1 }, api.nvim_buf_get_mark(0, 'z'))
  1863. eq({ 0, 1, 2, 0 }, fn.getpos("'z"))
  1864. end)
  1865. it('works with file/uppercase marks', function()
  1866. api.nvim_buf_set_lines(0, -1, -1, true, { 'a', 'bit of', 'text' })
  1867. eq(true, api.nvim_buf_set_mark(0, 'Z', 3, 2, {}))
  1868. eq({ 3, 2 }, api.nvim_buf_get_mark(0, 'Z'))
  1869. eq({ api.nvim_get_current_buf(), 3, 3, 0 }, fn.getpos("'Z"))
  1870. end)
  1871. it('fails when invalid marks names are used', function()
  1872. eq(false, pcall(api.nvim_buf_set_mark, 0, '!', 1, 0, {}))
  1873. eq(false, pcall(api.nvim_buf_set_mark, 0, 'fail', 1, 0, {}))
  1874. end)
  1875. it('fails when invalid buffer number is used', function()
  1876. eq(false, pcall(api.nvim_buf_set_mark, 99, 'a', 1, 1, {}))
  1877. end)
  1878. end)
  1879. describe('nvim_buf_del_mark', function()
  1880. it('works with buffer local marks', function()
  1881. api.nvim_buf_set_lines(0, -1, -1, true, { 'a', 'bit of', 'text' })
  1882. api.nvim_buf_set_mark(0, 'z', 3, 1, {})
  1883. eq(true, api.nvim_buf_del_mark(0, 'z'))
  1884. eq({ 0, 0 }, api.nvim_buf_get_mark(0, 'z'))
  1885. end)
  1886. it('works with file/uppercase marks', function()
  1887. api.nvim_buf_set_lines(0, -1, -1, true, { 'a', 'bit of', 'text' })
  1888. api.nvim_buf_set_mark(0, 'Z', 3, 3, {})
  1889. eq(true, api.nvim_buf_del_mark(0, 'Z'))
  1890. eq({ 0, 0 }, api.nvim_buf_get_mark(0, 'Z'))
  1891. end)
  1892. it('returns false in marks not set in this buffer', function()
  1893. local abuf = api.nvim_create_buf(false, true)
  1894. api.nvim_buf_set_lines(abuf, -1, -1, true, { 'a', 'bit of', 'text' })
  1895. api.nvim_buf_set_mark(abuf, 'A', 2, 2, {})
  1896. eq(false, api.nvim_buf_del_mark(0, 'A'))
  1897. eq({ 2, 2 }, api.nvim_buf_get_mark(abuf, 'A'))
  1898. end)
  1899. it('returns false if mark was not deleted', function()
  1900. api.nvim_buf_set_lines(0, -1, -1, true, { 'a', 'bit of', 'text' })
  1901. api.nvim_buf_set_mark(0, 'z', 3, 1, {})
  1902. eq(true, api.nvim_buf_del_mark(0, 'z'))
  1903. eq(false, api.nvim_buf_del_mark(0, 'z')) -- Mark was already deleted
  1904. end)
  1905. it('fails when invalid marks names are used', function()
  1906. eq(false, pcall(api.nvim_buf_del_mark, 0, '!'))
  1907. eq(false, pcall(api.nvim_buf_del_mark, 0, 'fail'))
  1908. end)
  1909. it('fails when invalid buffer number is used', function()
  1910. eq(false, pcall(api.nvim_buf_del_mark, 99, 'a'))
  1911. end)
  1912. end)
  1913. end)