window_spec.lua 97 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935
  1. local t = require('test.testutil')
  2. local n = require('test.functional.testnvim')()
  3. local Screen = require('test.functional.ui.screen')
  4. local clear, curbuf, curbuf_contents, curwin, eq, neq, matches, ok, feed, insert, eval =
  5. n.clear,
  6. n.api.nvim_get_current_buf,
  7. n.curbuf_contents,
  8. n.api.nvim_get_current_win,
  9. t.eq,
  10. t.neq,
  11. t.matches,
  12. t.ok,
  13. n.feed,
  14. n.insert,
  15. n.eval
  16. local poke_eventloop = n.poke_eventloop
  17. local exec = n.exec
  18. local exec_lua = n.exec_lua
  19. local fn = n.fn
  20. local request = n.request
  21. local NIL = vim.NIL
  22. local api = n.api
  23. local command = n.command
  24. local pcall_err = t.pcall_err
  25. local assert_alive = n.assert_alive
  26. describe('API/win', function()
  27. before_each(clear)
  28. describe('get_buf', function()
  29. it('works', function()
  30. eq(curbuf(), api.nvim_win_get_buf(api.nvim_list_wins()[1]))
  31. command('new')
  32. api.nvim_set_current_win(api.nvim_list_wins()[2])
  33. eq(curbuf(), api.nvim_win_get_buf(api.nvim_list_wins()[2]))
  34. neq(
  35. api.nvim_win_get_buf(api.nvim_list_wins()[1]),
  36. api.nvim_win_get_buf(api.nvim_list_wins()[2])
  37. )
  38. end)
  39. end)
  40. describe('set_buf', function()
  41. it('works', function()
  42. command('new')
  43. local windows = api.nvim_list_wins()
  44. neq(api.nvim_win_get_buf(windows[2]), api.nvim_win_get_buf(windows[1]))
  45. api.nvim_win_set_buf(windows[2], api.nvim_win_get_buf(windows[1]))
  46. eq(api.nvim_win_get_buf(windows[2]), api.nvim_win_get_buf(windows[1]))
  47. end)
  48. it('validates args', function()
  49. eq('Invalid buffer id: 23', pcall_err(api.nvim_win_set_buf, api.nvim_get_current_win(), 23))
  50. eq('Invalid window id: 23', pcall_err(api.nvim_win_set_buf, 23, api.nvim_get_current_buf()))
  51. end)
  52. it('disallowed in cmdwin if win=cmdwin_{old_cur}win or buf=cmdwin_buf', function()
  53. local new_buf = api.nvim_create_buf(true, true)
  54. local old_win = api.nvim_get_current_win()
  55. local new_win = api.nvim_open_win(new_buf, false, {
  56. relative = 'editor',
  57. row = 10,
  58. col = 10,
  59. width = 50,
  60. height = 10,
  61. })
  62. feed('q:')
  63. eq(
  64. 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
  65. pcall_err(api.nvim_win_set_buf, 0, new_buf)
  66. )
  67. eq(
  68. 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
  69. pcall_err(api.nvim_win_set_buf, old_win, new_buf)
  70. )
  71. eq(
  72. 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
  73. pcall_err(api.nvim_win_set_buf, new_win, 0)
  74. )
  75. matches(
  76. 'E11: Invalid in command%-line window; <CR> executes, CTRL%-C quits$',
  77. pcall_err(
  78. exec_lua,
  79. [[
  80. local cmdwin_buf = vim.api.nvim_get_current_buf()
  81. local new_win, new_buf = ...
  82. vim._with({buf = new_buf}, function()
  83. vim.api.nvim_win_set_buf(new_win, cmdwin_buf)
  84. end)
  85. ]],
  86. new_win,
  87. new_buf
  88. )
  89. )
  90. matches(
  91. 'E11: Invalid in command%-line window; <CR> executes, CTRL%-C quits$',
  92. pcall_err(
  93. exec_lua,
  94. [[
  95. local cmdwin_win = vim.api.nvim_get_current_win()
  96. local new_win, new_buf = ...
  97. vim._with({win = new_win}, function()
  98. vim.api.nvim_win_set_buf(cmdwin_win, new_buf)
  99. end)
  100. ]],
  101. new_win,
  102. new_buf
  103. )
  104. )
  105. local next_buf = api.nvim_create_buf(true, true)
  106. api.nvim_win_set_buf(new_win, next_buf)
  107. eq(next_buf, api.nvim_win_get_buf(new_win))
  108. end)
  109. describe("with 'autochdir'", function()
  110. local topdir
  111. local otherbuf
  112. local oldwin
  113. local newwin
  114. before_each(function()
  115. command('set shellslash')
  116. topdir = fn.getcwd()
  117. t.mkdir(topdir .. '/Xacd')
  118. t.mkdir(topdir .. '/Xacd/foo')
  119. otherbuf = api.nvim_create_buf(false, true)
  120. api.nvim_buf_set_name(otherbuf, topdir .. '/Xacd/baz.txt')
  121. command('set autochdir')
  122. command('edit Xacd/foo/bar.txt')
  123. eq(topdir .. '/Xacd/foo', fn.getcwd())
  124. oldwin = api.nvim_get_current_win()
  125. command('vsplit')
  126. newwin = api.nvim_get_current_win()
  127. end)
  128. after_each(function()
  129. n.rmdir(topdir .. '/Xacd')
  130. end)
  131. it('does not change cwd with non-current window', function()
  132. api.nvim_win_set_buf(oldwin, otherbuf)
  133. eq(topdir .. '/Xacd/foo', fn.getcwd())
  134. end)
  135. it('changes cwd with current window', function()
  136. api.nvim_win_set_buf(newwin, otherbuf)
  137. eq(topdir .. '/Xacd', fn.getcwd())
  138. end)
  139. end)
  140. end)
  141. describe('{get,set}_cursor', function()
  142. it('works', function()
  143. eq({ 1, 0 }, api.nvim_win_get_cursor(0))
  144. command('normal ityping\027o some text')
  145. eq('typing\n some text', curbuf_contents())
  146. eq({ 2, 10 }, api.nvim_win_get_cursor(0))
  147. api.nvim_win_set_cursor(0, { 2, 6 })
  148. command('normal i dumb')
  149. eq('typing\n some dumb text', curbuf_contents())
  150. end)
  151. it('no memory leak when using invalid window ID with invalid pos', function()
  152. eq('Invalid window id: 1', pcall_err(api.nvim_win_set_cursor, 1, { 'b\na' }))
  153. end)
  154. it('updates the screen, and also when the window is unfocused', function()
  155. local screen = Screen.new(30, 9)
  156. insert('prologue')
  157. feed('100o<esc>')
  158. insert('epilogue')
  159. local win = curwin()
  160. feed('gg')
  161. screen:expect {
  162. grid = [[
  163. ^prologue |
  164. |*8
  165. ]],
  166. }
  167. -- cursor position is at beginning
  168. eq({ 1, 0 }, api.nvim_win_get_cursor(win))
  169. -- move cursor to end
  170. api.nvim_win_set_cursor(win, { 101, 0 })
  171. screen:expect {
  172. grid = [[
  173. |*7
  174. ^epilogue |
  175. |
  176. ]],
  177. }
  178. -- move cursor to the beginning again
  179. api.nvim_win_set_cursor(win, { 1, 0 })
  180. screen:expect {
  181. grid = [[
  182. ^prologue |
  183. |*8
  184. ]],
  185. }
  186. -- move focus to new window
  187. command('new')
  188. neq(win, curwin())
  189. -- sanity check, cursor position is kept
  190. eq({ 1, 0 }, api.nvim_win_get_cursor(win))
  191. screen:expect {
  192. grid = [[
  193. ^ |
  194. {1:~ }|*2
  195. {3:[No Name] }|
  196. prologue |
  197. |*2
  198. {2:[No Name] [+] }|
  199. |
  200. ]],
  201. }
  202. -- move cursor to end
  203. api.nvim_win_set_cursor(win, { 101, 0 })
  204. screen:expect {
  205. grid = [[
  206. ^ |
  207. {1:~ }|*2
  208. {3:[No Name] }|
  209. |*2
  210. epilogue |
  211. {2:[No Name] [+] }|
  212. |
  213. ]],
  214. }
  215. -- move cursor to the beginning again
  216. api.nvim_win_set_cursor(win, { 1, 0 })
  217. screen:expect {
  218. grid = [[
  219. ^ |
  220. {1:~ }|*2
  221. {3:[No Name] }|
  222. prologue |
  223. |*2
  224. {2:[No Name] [+] }|
  225. |
  226. ]],
  227. }
  228. -- curwin didn't change back
  229. neq(win, curwin())
  230. end)
  231. it('remembers what column it wants to be in', function()
  232. insert('first line')
  233. feed('o<esc>')
  234. insert('second line')
  235. feed('gg')
  236. poke_eventloop() -- let nvim process the 'gg' command
  237. -- cursor position is at beginning
  238. local win = curwin()
  239. eq({ 1, 0 }, api.nvim_win_get_cursor(win))
  240. -- move cursor to column 5
  241. api.nvim_win_set_cursor(win, { 1, 5 })
  242. -- move down a line
  243. feed('j')
  244. poke_eventloop() -- let nvim process the 'j' command
  245. -- cursor is still in column 5
  246. eq({ 2, 5 }, api.nvim_win_get_cursor(win))
  247. end)
  248. it('updates cursorline and statusline ruler in non-current window', function()
  249. local screen = Screen.new(60, 8)
  250. command('set ruler')
  251. command('set cursorline')
  252. insert([[
  253. aaa
  254. bbb
  255. ccc
  256. ddd]])
  257. local oldwin = curwin()
  258. command('vsplit')
  259. screen:expect([[
  260. aaa │aaa |
  261. bbb │bbb |
  262. ccc │ccc |
  263. {21:dd^d }│{21:ddd }|
  264. {1:~ }│{1:~ }|*2
  265. {3:[No Name] [+] 4,3 All }{2:[No Name] [+] 4,3 All}|
  266. |
  267. ]])
  268. api.nvim_win_set_cursor(oldwin, { 1, 0 })
  269. screen:expect([[
  270. aaa │{21:aaa }|
  271. bbb │bbb |
  272. ccc │ccc |
  273. {21:dd^d }│ddd |
  274. {1:~ }│{1:~ }|*2
  275. {3:[No Name] [+] 4,3 All }{2:[No Name] [+] 1,1 All}|
  276. |
  277. ]])
  278. end)
  279. it('updates cursorcolumn in non-current window', function()
  280. local screen = Screen.new(60, 8)
  281. command('set cursorcolumn')
  282. insert([[
  283. aaa
  284. bbb
  285. ccc
  286. ddd]])
  287. local oldwin = curwin()
  288. command('vsplit')
  289. screen:expect([[
  290. aa{21:a} │aa{21:a} |
  291. bb{21:b} │bb{21:b} |
  292. cc{21:c} │cc{21:c} |
  293. dd^d │ddd |
  294. {1:~ }│{1:~ }|*2
  295. {3:[No Name] [+] }{2:[No Name] [+] }|
  296. |
  297. ]])
  298. api.nvim_win_set_cursor(oldwin, { 2, 0 })
  299. screen:expect([[
  300. aa{21:a} │{21:a}aa |
  301. bb{21:b} │bbb |
  302. cc{21:c} │{21:c}cc |
  303. dd^d │{21:d}dd |
  304. {1:~ }│{1:~ }|*2
  305. {3:[No Name] [+] }{2:[No Name] [+] }|
  306. |
  307. ]])
  308. end)
  309. end)
  310. describe('{get,set}_height', function()
  311. it('works', function()
  312. command('vsplit')
  313. eq(
  314. api.nvim_win_get_height(api.nvim_list_wins()[2]),
  315. api.nvim_win_get_height(api.nvim_list_wins()[1])
  316. )
  317. api.nvim_set_current_win(api.nvim_list_wins()[2])
  318. command('split')
  319. eq(
  320. api.nvim_win_get_height(api.nvim_list_wins()[2]),
  321. math.floor(api.nvim_win_get_height(api.nvim_list_wins()[1]) / 2)
  322. )
  323. api.nvim_win_set_height(api.nvim_list_wins()[2], 2)
  324. eq(2, api.nvim_win_get_height(api.nvim_list_wins()[2]))
  325. end)
  326. it('failure modes', function()
  327. command('split')
  328. eq('Invalid window id: 999999', pcall_err(api.nvim_win_set_height, 999999, 10))
  329. eq(
  330. 'Wrong type for argument 2 when calling nvim_win_set_height, expecting Integer',
  331. pcall_err(api.nvim_win_set_height, 0, 0.9)
  332. )
  333. end)
  334. it('correctly handles height=1', function()
  335. command('split')
  336. api.nvim_set_current_win(api.nvim_list_wins()[1])
  337. api.nvim_win_set_height(api.nvim_list_wins()[2], 1)
  338. eq(1, api.nvim_win_get_height(api.nvim_list_wins()[2]))
  339. end)
  340. it('correctly handles height=1 with a winbar', function()
  341. command('set winbar=foobar')
  342. command('set winminheight=0')
  343. command('split')
  344. api.nvim_set_current_win(api.nvim_list_wins()[1])
  345. api.nvim_win_set_height(api.nvim_list_wins()[2], 1)
  346. eq(1, api.nvim_win_get_height(api.nvim_list_wins()[2]))
  347. end)
  348. it('do not cause ml_get errors with foldmethod=expr #19989', function()
  349. insert([[
  350. aaaaa
  351. bbbbb
  352. ccccc]])
  353. command('set foldmethod=expr')
  354. exec([[
  355. new
  356. let w = nvim_get_current_win()
  357. wincmd w
  358. call nvim_win_set_height(w, 5)
  359. ]])
  360. feed('l')
  361. eq('', api.nvim_get_vvar('errmsg'))
  362. end)
  363. end)
  364. describe('{get,set}_width', function()
  365. it('works', function()
  366. command('split')
  367. eq(
  368. api.nvim_win_get_width(api.nvim_list_wins()[2]),
  369. api.nvim_win_get_width(api.nvim_list_wins()[1])
  370. )
  371. api.nvim_set_current_win(api.nvim_list_wins()[2])
  372. command('vsplit')
  373. eq(
  374. api.nvim_win_get_width(api.nvim_list_wins()[2]),
  375. math.floor(api.nvim_win_get_width(api.nvim_list_wins()[1]) / 2)
  376. )
  377. api.nvim_win_set_width(api.nvim_list_wins()[2], 2)
  378. eq(2, api.nvim_win_get_width(api.nvim_list_wins()[2]))
  379. end)
  380. it('failure modes', function()
  381. command('vsplit')
  382. eq('Invalid window id: 999999', pcall_err(api.nvim_win_set_width, 999999, 10))
  383. eq(
  384. 'Wrong type for argument 2 when calling nvim_win_set_width, expecting Integer',
  385. pcall_err(api.nvim_win_set_width, 0, 0.9)
  386. )
  387. end)
  388. it('do not cause ml_get errors with foldmethod=expr #19989', function()
  389. insert([[
  390. aaaaa
  391. bbbbb
  392. ccccc]])
  393. command('set foldmethod=expr')
  394. exec([[
  395. vnew
  396. let w = nvim_get_current_win()
  397. wincmd w
  398. call nvim_win_set_width(w, 5)
  399. ]])
  400. feed('l')
  401. eq('', api.nvim_get_vvar('errmsg'))
  402. end)
  403. end)
  404. describe('{get,set,del}_var', function()
  405. it('works', function()
  406. api.nvim_win_set_var(0, 'lua', { 1, 2, { ['3'] = 1 } })
  407. eq({ 1, 2, { ['3'] = 1 } }, api.nvim_win_get_var(0, 'lua'))
  408. eq({ 1, 2, { ['3'] = 1 } }, api.nvim_eval('w:lua'))
  409. eq(1, fn.exists('w:lua'))
  410. api.nvim_win_del_var(0, 'lua')
  411. eq(0, fn.exists('w:lua'))
  412. eq('Key not found: lua', pcall_err(api.nvim_win_del_var, 0, 'lua'))
  413. api.nvim_win_set_var(0, 'lua', 1)
  414. command('lockvar w:lua')
  415. eq('Key is locked: lua', pcall_err(api.nvim_win_del_var, 0, 'lua'))
  416. eq('Key is locked: lua', pcall_err(api.nvim_win_set_var, 0, 'lua', 1))
  417. end)
  418. it('window_set_var returns the old value', function()
  419. local val1 = { 1, 2, { ['3'] = 1 } }
  420. local val2 = { 4, 7 }
  421. eq(NIL, request('window_set_var', 0, 'lua', val1))
  422. eq(val1, request('window_set_var', 0, 'lua', val2))
  423. end)
  424. it('window_del_var returns the old value', function()
  425. local val1 = { 1, 2, { ['3'] = 1 } }
  426. local val2 = { 4, 7 }
  427. eq(NIL, request('window_set_var', 0, 'lua', val1))
  428. eq(val1, request('window_set_var', 0, 'lua', val2))
  429. eq(val2, request('window_del_var', 0, 'lua'))
  430. end)
  431. end)
  432. describe('nvim_get_option_value, nvim_set_option_value', function()
  433. it('works', function()
  434. api.nvim_set_option_value('colorcolumn', '4,3', {})
  435. eq('4,3', api.nvim_get_option_value('colorcolumn', {}))
  436. command('set modified hidden')
  437. command('enew') -- edit new buffer, window option is preserved
  438. eq('4,3', api.nvim_get_option_value('colorcolumn', {}))
  439. -- global-local option
  440. api.nvim_set_option_value('statusline', 'window-status', { win = 0 })
  441. eq('window-status', api.nvim_get_option_value('statusline', { win = 0 }))
  442. eq('', api.nvim_get_option_value('statusline', { scope = 'global' }))
  443. command('set modified')
  444. command('enew') -- global-local: not preserved in new buffer
  445. -- confirm local value was not copied
  446. eq('', api.nvim_get_option_value('statusline', { win = 0 }))
  447. eq('', eval('&l:statusline'))
  448. end)
  449. it('after switching windows #15390', function()
  450. command('tabnew')
  451. local tab1 = unpack(api.nvim_list_tabpages())
  452. local win1 = unpack(api.nvim_tabpage_list_wins(tab1))
  453. api.nvim_set_option_value('statusline', 'window-status', { win = win1 })
  454. command('split')
  455. command('wincmd J')
  456. command('wincmd j')
  457. eq('window-status', api.nvim_get_option_value('statusline', { win = win1 }))
  458. assert_alive()
  459. end)
  460. describe('after closing', function()
  461. local buf, win0, win1, win2
  462. before_each(function()
  463. win0 = api.nvim_get_current_win()
  464. command('new')
  465. buf = api.nvim_get_current_buf()
  466. win1 = api.nvim_get_current_win()
  467. command('set numberwidth=10')
  468. command('split')
  469. win2 = api.nvim_get_current_win()
  470. command('set numberwidth=15')
  471. command('enew')
  472. api.nvim_set_current_win(win1)
  473. command('normal ix')
  474. command('enew')
  475. api.nvim_set_current_win(win0)
  476. eq(4, api.nvim_get_option_value('numberwidth', {}))
  477. end)
  478. -- at this point buffer `buf` is current in no windows. Closing shouldn't affect its defaults
  479. it('0 windows', function()
  480. api.nvim_set_current_buf(buf)
  481. eq(10, api.nvim_get_option_value('numberwidth', {}))
  482. end)
  483. it('1 window', function()
  484. api.nvim_win_close(win1, false)
  485. api.nvim_set_current_buf(buf)
  486. eq(10, api.nvim_get_option_value('numberwidth', {}))
  487. end)
  488. it('2 windows', function()
  489. api.nvim_win_close(win1, false)
  490. api.nvim_win_close(win2, false)
  491. api.nvim_set_current_buf(buf)
  492. eq(10, api.nvim_get_option_value('numberwidth', {}))
  493. end)
  494. end)
  495. it('returns values for unset local options', function()
  496. eq(-1, api.nvim_get_option_value('scrolloff', { win = 0, scope = 'local' }))
  497. end)
  498. end)
  499. describe('get_position', function()
  500. it('works', function()
  501. local height = api.nvim_win_get_height(api.nvim_list_wins()[1])
  502. local width = api.nvim_win_get_width(api.nvim_list_wins()[1])
  503. command('split')
  504. command('vsplit')
  505. eq({ 0, 0 }, api.nvim_win_get_position(api.nvim_list_wins()[1]))
  506. local vsplit_pos = math.floor(width / 2)
  507. local split_pos = math.floor(height / 2)
  508. local win2row, win2col = unpack(api.nvim_win_get_position(api.nvim_list_wins()[2]))
  509. local win3row, win3col = unpack(api.nvim_win_get_position(api.nvim_list_wins()[3]))
  510. eq(0, win2row)
  511. eq(0, win3col)
  512. ok(vsplit_pos - 1 <= win2col and win2col <= vsplit_pos + 1)
  513. ok(split_pos - 1 <= win3row and win3row <= split_pos + 1)
  514. end)
  515. end)
  516. describe('get_position', function()
  517. it('works', function()
  518. command('tabnew')
  519. command('vsplit')
  520. eq(api.nvim_win_get_tabpage(api.nvim_list_wins()[1]), api.nvim_list_tabpages()[1])
  521. eq(api.nvim_win_get_tabpage(api.nvim_list_wins()[2]), api.nvim_list_tabpages()[2])
  522. eq(api.nvim_win_get_tabpage(api.nvim_list_wins()[3]), api.nvim_list_tabpages()[2])
  523. end)
  524. end)
  525. describe('get_number', function()
  526. it('works', function()
  527. local wins = api.nvim_list_wins()
  528. eq(1, api.nvim_win_get_number(wins[1]))
  529. command('split')
  530. local win1, win2 = unpack(api.nvim_list_wins())
  531. eq(1, api.nvim_win_get_number(win1))
  532. eq(2, api.nvim_win_get_number(win2))
  533. command('wincmd J')
  534. eq(2, api.nvim_win_get_number(win1))
  535. eq(1, api.nvim_win_get_number(win2))
  536. command('tabnew')
  537. local win3 = api.nvim_list_wins()[3]
  538. -- First tab page
  539. eq(2, api.nvim_win_get_number(win1))
  540. eq(1, api.nvim_win_get_number(win2))
  541. -- Second tab page
  542. eq(1, api.nvim_win_get_number(win3))
  543. end)
  544. end)
  545. describe('is_valid', function()
  546. it('works', function()
  547. command('split')
  548. local win = api.nvim_list_wins()[2]
  549. api.nvim_set_current_win(win)
  550. ok(api.nvim_win_is_valid(win))
  551. command('close')
  552. ok(not api.nvim_win_is_valid(win))
  553. end)
  554. end)
  555. describe('close', function()
  556. it('can close current window', function()
  557. local oldwin = api.nvim_get_current_win()
  558. command('split')
  559. local newwin = api.nvim_get_current_win()
  560. api.nvim_win_close(newwin, false)
  561. eq({ oldwin }, api.nvim_list_wins())
  562. end)
  563. it('can close noncurrent window', function()
  564. local oldwin = api.nvim_get_current_win()
  565. command('split')
  566. local newwin = api.nvim_get_current_win()
  567. api.nvim_win_close(oldwin, false)
  568. eq({ newwin }, api.nvim_list_wins())
  569. end)
  570. it("handles changed buffer when 'hidden' is unset", function()
  571. command('set nohidden')
  572. local oldwin = api.nvim_get_current_win()
  573. insert('text')
  574. command('new')
  575. local newwin = api.nvim_get_current_win()
  576. eq(
  577. 'Vim:E37: No write since last change (add ! to override)',
  578. pcall_err(api.nvim_win_close, oldwin, false)
  579. )
  580. eq({ newwin, oldwin }, api.nvim_list_wins())
  581. end)
  582. it('handles changed buffer with force', function()
  583. local oldwin = api.nvim_get_current_win()
  584. insert('text')
  585. command('new')
  586. local newwin = api.nvim_get_current_win()
  587. api.nvim_win_close(oldwin, true)
  588. eq({ newwin }, api.nvim_list_wins())
  589. end)
  590. it('in cmdline-window #9767', function()
  591. command('split')
  592. eq(2, #api.nvim_list_wins())
  593. local oldbuf = api.nvim_get_current_buf()
  594. local oldwin = api.nvim_get_current_win()
  595. local otherwin = api.nvim_open_win(0, false, {
  596. relative = 'editor',
  597. row = 10,
  598. col = 10,
  599. width = 10,
  600. height = 10,
  601. })
  602. -- Open cmdline-window.
  603. feed('q:')
  604. eq(4, #api.nvim_list_wins())
  605. eq(':', fn.getcmdwintype())
  606. -- Not allowed to close previous window from cmdline-window.
  607. eq(
  608. 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
  609. pcall_err(api.nvim_win_close, oldwin, true)
  610. )
  611. -- Closing other windows is fine.
  612. api.nvim_win_close(otherwin, true)
  613. eq(false, api.nvim_win_is_valid(otherwin))
  614. -- Close cmdline-window.
  615. api.nvim_win_close(0, true)
  616. eq(2, #api.nvim_list_wins())
  617. eq('', fn.getcmdwintype())
  618. -- Closing curwin in context of a different window shouldn't close cmdwin.
  619. otherwin = api.nvim_open_win(0, false, {
  620. relative = 'editor',
  621. row = 10,
  622. col = 10,
  623. width = 10,
  624. height = 10,
  625. })
  626. feed('q:')
  627. exec_lua(
  628. [[
  629. vim._with({win = ...}, function()
  630. vim.api.nvim_win_close(0, true)
  631. end)
  632. ]],
  633. otherwin
  634. )
  635. eq(false, api.nvim_win_is_valid(otherwin))
  636. eq(':', fn.getcmdwintype())
  637. -- Closing cmdwin in context of a non-previous window is still OK.
  638. otherwin = api.nvim_open_win(oldbuf, false, {
  639. relative = 'editor',
  640. row = 10,
  641. col = 10,
  642. width = 10,
  643. height = 10,
  644. })
  645. exec_lua(
  646. [[
  647. local otherwin, cmdwin = ...
  648. vim._with({win = otherwin}, function()
  649. vim.api.nvim_win_close(cmdwin, true)
  650. end)
  651. ]],
  652. otherwin,
  653. api.nvim_get_current_win()
  654. )
  655. eq('', fn.getcmdwintype())
  656. eq(true, api.nvim_win_is_valid(otherwin))
  657. end)
  658. it('closing current (float) window of another tabpage #15313', function()
  659. command('tabedit')
  660. command('botright split')
  661. local prevwin = curwin()
  662. eq(2, eval('tabpagenr()'))
  663. local win = api.nvim_open_win(0, true, {
  664. relative = 'editor',
  665. row = 10,
  666. col = 10,
  667. width = 50,
  668. height = 10,
  669. })
  670. local tab = eval('tabpagenr()')
  671. command('tabprevious')
  672. eq(1, eval('tabpagenr()'))
  673. api.nvim_win_close(win, false)
  674. eq(prevwin, api.nvim_tabpage_get_win(tab))
  675. assert_alive()
  676. end)
  677. end)
  678. describe('hide', function()
  679. it('can hide current window', function()
  680. local oldwin = api.nvim_get_current_win()
  681. command('split')
  682. local newwin = api.nvim_get_current_win()
  683. api.nvim_win_hide(newwin)
  684. eq({ oldwin }, api.nvim_list_wins())
  685. end)
  686. it('can hide noncurrent window', function()
  687. local oldwin = api.nvim_get_current_win()
  688. command('split')
  689. local newwin = api.nvim_get_current_win()
  690. api.nvim_win_hide(oldwin)
  691. eq({ newwin }, api.nvim_list_wins())
  692. end)
  693. it('does not close the buffer', function()
  694. local oldwin = api.nvim_get_current_win()
  695. local oldbuf = api.nvim_get_current_buf()
  696. local buf = api.nvim_create_buf(true, false)
  697. local newwin = api.nvim_open_win(buf, true, {
  698. relative = 'win',
  699. row = 3,
  700. col = 3,
  701. width = 12,
  702. height = 3,
  703. })
  704. api.nvim_win_hide(newwin)
  705. eq({ oldwin }, api.nvim_list_wins())
  706. eq({ oldbuf, buf }, api.nvim_list_bufs())
  707. end)
  708. it('deletes the buffer when bufhidden=wipe', function()
  709. local oldwin = api.nvim_get_current_win()
  710. local oldbuf = api.nvim_get_current_buf()
  711. local buf = api.nvim_create_buf(true, false)
  712. local newwin = api.nvim_open_win(buf, true, {
  713. relative = 'win',
  714. row = 3,
  715. col = 3,
  716. width = 12,
  717. height = 3,
  718. })
  719. api.nvim_set_option_value('bufhidden', 'wipe', { buf = buf })
  720. api.nvim_win_hide(newwin)
  721. eq({ oldwin }, api.nvim_list_wins())
  722. eq({ oldbuf }, api.nvim_list_bufs())
  723. end)
  724. it('in the cmdwin', function()
  725. feed('q:')
  726. -- Can close the cmdwin.
  727. api.nvim_win_hide(0)
  728. eq('', fn.getcmdwintype())
  729. local old_buf = api.nvim_get_current_buf()
  730. local old_win = api.nvim_get_current_win()
  731. local other_win = api.nvim_open_win(0, false, {
  732. relative = 'win',
  733. row = 3,
  734. col = 3,
  735. width = 12,
  736. height = 3,
  737. })
  738. feed('q:')
  739. -- Cannot close the previous window.
  740. eq(
  741. 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
  742. pcall_err(api.nvim_win_hide, old_win)
  743. )
  744. -- Can close other windows.
  745. api.nvim_win_hide(other_win)
  746. eq(false, api.nvim_win_is_valid(other_win))
  747. -- Closing curwin in context of a different window shouldn't close cmdwin.
  748. other_win = api.nvim_open_win(old_buf, false, {
  749. relative = 'editor',
  750. row = 10,
  751. col = 10,
  752. width = 10,
  753. height = 10,
  754. })
  755. exec_lua(
  756. [[
  757. vim._with({win = ...}, function()
  758. vim.api.nvim_win_hide(0)
  759. end)
  760. ]],
  761. other_win
  762. )
  763. eq(false, api.nvim_win_is_valid(other_win))
  764. eq(':', fn.getcmdwintype())
  765. -- Closing cmdwin in context of a non-previous window is still OK.
  766. other_win = api.nvim_open_win(old_buf, false, {
  767. relative = 'editor',
  768. row = 10,
  769. col = 10,
  770. width = 10,
  771. height = 10,
  772. })
  773. exec_lua(
  774. [[
  775. local otherwin, cmdwin = ...
  776. vim._with({win = otherwin}, function()
  777. vim.api.nvim_win_hide(cmdwin)
  778. end)
  779. ]],
  780. other_win,
  781. api.nvim_get_current_win()
  782. )
  783. eq('', fn.getcmdwintype())
  784. eq(true, api.nvim_win_is_valid(other_win))
  785. end)
  786. end)
  787. describe('text_height', function()
  788. it('validation', function()
  789. local X = api.nvim_get_vvar('maxcol')
  790. insert([[
  791. aaa
  792. bbb
  793. ccc
  794. ddd
  795. eee]])
  796. eq('Invalid window id: 23', pcall_err(api.nvim_win_text_height, 23, {}))
  797. eq('Line index out of bounds', pcall_err(api.nvim_win_text_height, 0, { start_row = 5 }))
  798. eq('Line index out of bounds', pcall_err(api.nvim_win_text_height, 0, { start_row = -6 }))
  799. eq('Line index out of bounds', pcall_err(api.nvim_win_text_height, 0, { end_row = 5 }))
  800. eq('Line index out of bounds', pcall_err(api.nvim_win_text_height, 0, { end_row = -6 }))
  801. eq(
  802. "'start_row' is higher than 'end_row'",
  803. pcall_err(api.nvim_win_text_height, 0, { start_row = 3, end_row = 1 })
  804. )
  805. eq(
  806. "'start_vcol' specified without 'start_row'",
  807. pcall_err(api.nvim_win_text_height, 0, { end_row = 2, start_vcol = 0 })
  808. )
  809. eq(
  810. "'end_vcol' specified without 'end_row'",
  811. pcall_err(api.nvim_win_text_height, 0, { start_row = 2, end_vcol = 0 })
  812. )
  813. eq(
  814. "Invalid 'start_vcol': out of range",
  815. pcall_err(api.nvim_win_text_height, 0, { start_row = 2, start_vcol = -1 })
  816. )
  817. eq(
  818. "Invalid 'start_vcol': out of range",
  819. pcall_err(api.nvim_win_text_height, 0, { start_row = 2, start_vcol = X + 1 })
  820. )
  821. eq(
  822. "Invalid 'end_vcol': out of range",
  823. pcall_err(api.nvim_win_text_height, 0, { end_row = 2, end_vcol = -1 })
  824. )
  825. eq(
  826. "Invalid 'end_vcol': out of range",
  827. pcall_err(api.nvim_win_text_height, 0, { end_row = 2, end_vcol = X + 1 })
  828. )
  829. eq(
  830. "'start_vcol' is higher than 'end_vcol'",
  831. pcall_err(
  832. api.nvim_win_text_height,
  833. 0,
  834. { start_row = 2, end_row = 2, start_vcol = 10, end_vcol = 5 }
  835. )
  836. )
  837. end)
  838. it('with two diff windows', function()
  839. local X = api.nvim_get_vvar('maxcol')
  840. local screen = Screen.new(45, 22)
  841. exec([[
  842. set diffopt+=context:2 number
  843. let expr = 'printf("%08d", v:val) .. repeat("!", v:val)'
  844. call setline(1, map(range(1, 20) + range(25, 45), expr))
  845. vnew
  846. call setline(1, map(range(3, 20) + range(28, 50), expr))
  847. windo diffthis
  848. ]])
  849. feed('24gg')
  850. screen:expect {
  851. grid = [[
  852. {7: }{8: }{23:----------------}│{7: }{8: 1 }{22:00000001! }|
  853. {7: }{8: }{23:----------------}│{7: }{8: 2 }{22:00000002!! }|
  854. {7: }{8: 1 }00000003!!! │{7: }{8: 3 }00000003!!! |
  855. {7: }{8: 2 }00000004!!!! │{7: }{8: 4 }00000004!!!! |
  856. {7:+ }{8: 3 }{13:+-- 14 lines: 00}│{7:+ }{8: 5 }{13:+-- 14 lines: 00}|
  857. {7: }{8: 17 }00000019!!!!!!!!│{7: }{8: 19 }00000019!!!!!!!!|
  858. {7: }{8: 18 }00000020!!!!!!!!│{7: }{8: 20 }00000020!!!!!!!!|
  859. {7: }{8: }{23:----------------}│{7: }{8: 21 }{22:00000025!!!!!!!!}|
  860. {7: }{8: }{23:----------------}│{7: }{8: 22 }{22:00000026!!!!!!!!}|
  861. {7: }{8: }{23:----------------}│{7: }{8: 23 }{22:00000027!!!!!!!!}|
  862. {7: }{8: 19 }00000028!!!!!!!!│{7: }{8: 24 }^00000028!!!!!!!!|
  863. {7: }{8: 20 }00000029!!!!!!!!│{7: }{8: 25 }00000029!!!!!!!!|
  864. {7:+ }{8: 21 }{13:+-- 14 lines: 00}│{7:+ }{8: 26 }{13:+-- 14 lines: 00}|
  865. {7: }{8: 35 }00000044!!!!!!!!│{7: }{8: 40 }00000044!!!!!!!!|
  866. {7: }{8: 36 }00000045!!!!!!!!│{7: }{8: 41 }00000045!!!!!!!!|
  867. {7: }{8: 37 }{22:00000046!!!!!!!!}│{7: }{8: }{23:----------------}|
  868. {7: }{8: 38 }{22:00000047!!!!!!!!}│{7: }{8: }{23:----------------}|
  869. {7: }{8: 39 }{22:00000048!!!!!!!!}│{7: }{8: }{23:----------------}|
  870. {7: }{8: 40 }{22:00000049!!!!!!!!}│{7: }{8: }{23:----------------}|
  871. {7: }{8: 41 }{22:00000050!!!!!!!!}│{7: }{8: }{23:----------------}|
  872. {2:[No Name] [+] }{3:[No Name] [+] }|
  873. |
  874. ]],
  875. }
  876. screen:try_resize(45, 3)
  877. screen:expect {
  878. grid = [[
  879. {7: }{8: 19 }00000028!!!!!!!!│{7: }{8: 24 }^00000028!!!!!!!!|
  880. {2:[No Name] [+] }{3:[No Name] [+] }|
  881. |
  882. ]],
  883. }
  884. eq({ all = 20, fill = 5 }, api.nvim_win_text_height(1000, {}))
  885. eq({ all = 20, fill = 5 }, api.nvim_win_text_height(1001, {}))
  886. eq({ all = 20, fill = 5 }, api.nvim_win_text_height(1000, { start_row = 0 }))
  887. eq({ all = 20, fill = 5 }, api.nvim_win_text_height(1001, { start_row = 0 }))
  888. eq({ all = 15, fill = 0 }, api.nvim_win_text_height(1000, { end_row = -1 }))
  889. eq({ all = 15, fill = 0 }, api.nvim_win_text_height(1000, { end_row = 40 }))
  890. eq({ all = 20, fill = 5 }, api.nvim_win_text_height(1001, { end_row = -1 }))
  891. eq({ all = 20, fill = 5 }, api.nvim_win_text_height(1001, { end_row = 40 }))
  892. eq({ all = 10, fill = 5 }, api.nvim_win_text_height(1000, { start_row = 23 }))
  893. eq({ all = 13, fill = 3 }, api.nvim_win_text_height(1001, { start_row = 18 }))
  894. eq({ all = 11, fill = 0 }, api.nvim_win_text_height(1000, { end_row = 23 }))
  895. eq({ all = 11, fill = 5 }, api.nvim_win_text_height(1001, { end_row = 18 }))
  896. eq({ all = 11, fill = 0 }, api.nvim_win_text_height(1000, { start_row = 3, end_row = 39 }))
  897. eq({ all = 11, fill = 3 }, api.nvim_win_text_height(1001, { start_row = 1, end_row = 34 }))
  898. eq({ all = 9, fill = 0 }, api.nvim_win_text_height(1000, { start_row = 4, end_row = 38 }))
  899. eq({ all = 9, fill = 3 }, api.nvim_win_text_height(1001, { start_row = 2, end_row = 33 }))
  900. eq({ all = 9, fill = 0 }, api.nvim_win_text_height(1000, { start_row = 5, end_row = 37 }))
  901. eq({ all = 9, fill = 3 }, api.nvim_win_text_height(1001, { start_row = 3, end_row = 32 }))
  902. eq({ all = 9, fill = 0 }, api.nvim_win_text_height(1000, { start_row = 17, end_row = 25 }))
  903. eq({ all = 9, fill = 3 }, api.nvim_win_text_height(1001, { start_row = 15, end_row = 20 }))
  904. eq({ all = 7, fill = 0 }, api.nvim_win_text_height(1000, { start_row = 18, end_row = 24 }))
  905. eq({ all = 7, fill = 3 }, api.nvim_win_text_height(1001, { start_row = 16, end_row = 19 }))
  906. eq({ all = 6, fill = 5 }, api.nvim_win_text_height(1000, { start_row = -1 }))
  907. eq({ all = 5, fill = 5 }, api.nvim_win_text_height(1000, { start_row = -1, start_vcol = X }))
  908. eq(
  909. { all = 0, fill = 0 },
  910. api.nvim_win_text_height(1000, { start_row = -1, start_vcol = X, end_row = -1 })
  911. )
  912. eq(
  913. { all = 0, fill = 0 },
  914. api.nvim_win_text_height(
  915. 1000,
  916. { start_row = -1, start_vcol = X, end_row = -1, end_vcol = X }
  917. )
  918. )
  919. eq(
  920. { all = 1, fill = 0 },
  921. api.nvim_win_text_height(
  922. 1000,
  923. { start_row = -1, start_vcol = 0, end_row = -1, end_vcol = X }
  924. )
  925. )
  926. eq({ all = 3, fill = 2 }, api.nvim_win_text_height(1001, { end_row = 0 }))
  927. eq({ all = 2, fill = 2 }, api.nvim_win_text_height(1001, { end_row = 0, end_vcol = 0 }))
  928. eq(
  929. { all = 2, fill = 2 },
  930. api.nvim_win_text_height(1001, { start_row = 0, end_row = 0, end_vcol = 0 })
  931. )
  932. eq(
  933. { all = 0, fill = 0 },
  934. api.nvim_win_text_height(1001, { start_row = 0, start_vcol = 0, end_row = 0, end_vcol = 0 })
  935. )
  936. eq(
  937. { all = 1, fill = 0 },
  938. api.nvim_win_text_height(1001, { start_row = 0, start_vcol = 0, end_row = 0, end_vcol = X })
  939. )
  940. eq({ all = 11, fill = 5 }, api.nvim_win_text_height(1001, { end_row = 18 }))
  941. eq(
  942. { all = 9, fill = 3 },
  943. api.nvim_win_text_height(1001, { start_row = 0, start_vcol = 0, end_row = 18 })
  944. )
  945. eq({ all = 10, fill = 5 }, api.nvim_win_text_height(1001, { end_row = 18, end_vcol = 0 }))
  946. eq(
  947. { all = 8, fill = 3 },
  948. api.nvim_win_text_height(
  949. 1001,
  950. { start_row = 0, start_vcol = 0, end_row = 18, end_vcol = 0 }
  951. )
  952. )
  953. end)
  954. it('with wrapped lines', function()
  955. local X = api.nvim_get_vvar('maxcol')
  956. local screen = Screen.new(45, 22)
  957. exec([[
  958. set number cpoptions+=n
  959. call setline(1, repeat([repeat('foobar-', 36)], 3))
  960. ]])
  961. local ns = api.nvim_create_namespace('')
  962. api.nvim_buf_set_extmark(
  963. 0,
  964. ns,
  965. 1,
  966. 100,
  967. { virt_text = { { ('?'):rep(15), 'Search' } }, virt_text_pos = 'inline' }
  968. )
  969. api.nvim_buf_set_extmark(
  970. 0,
  971. ns,
  972. 2,
  973. 200,
  974. { virt_text = { { ('!'):rep(75), 'Search' } }, virt_text_pos = 'inline' }
  975. )
  976. screen:expect {
  977. grid = [[
  978. {8: 1 }^foobar-foobar-foobar-foobar-foobar-foobar|
  979. -foobar-foobar-foobar-foobar-foobar-foobar-fo|
  980. obar-foobar-foobar-foobar-foobar-foobar-fooba|
  981. r-foobar-foobar-foobar-foobar-foobar-foobar-f|
  982. oobar-foobar-foobar-foobar-foobar-foobar-foob|
  983. ar-foobar-foobar-foobar-foobar- |
  984. {8: 2 }foobar-foobar-foobar-foobar-foobar-foobar|
  985. -foobar-foobar-foobar-foobar-foobar-foobar-fo|
  986. obar-foobar-fo{10:???????????????}obar-foobar-foob|
  987. ar-foobar-foobar-foobar-foobar-foobar-foobar-|
  988. foobar-foobar-foobar-foobar-foobar-foobar-foo|
  989. bar-foobar-foobar-foobar-foobar-foobar-foobar|
  990. - |
  991. {8: 3 }foobar-foobar-foobar-foobar-foobar-foobar|
  992. -foobar-foobar-foobar-foobar-foobar-foobar-fo|
  993. obar-foobar-foobar-foobar-foobar-foobar-fooba|
  994. r-foobar-foobar-foobar-foobar-foobar-foobar-f|
  995. oobar-foobar-foobar-foob{10:!!!!!!!!!!!!!!!!!!!!!}|
  996. {10:!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!}|
  997. {10:!!!!!!!!!}ar-foobar-foobar-foobar-foobar-fooba|
  998. r-foobar-foobar- |
  999. |
  1000. ]],
  1001. }
  1002. screen:try_resize(45, 2)
  1003. screen:expect {
  1004. grid = [[
  1005. {8: 1 }^foobar-foobar-foobar-foobar-foobar-foobar|
  1006. |
  1007. ]],
  1008. }
  1009. eq({ all = 21, fill = 0 }, api.nvim_win_text_height(0, {}))
  1010. eq({ all = 6, fill = 0 }, api.nvim_win_text_height(0, { start_row = 0, end_row = 0 }))
  1011. eq({ all = 7, fill = 0 }, api.nvim_win_text_height(0, { start_row = 1, end_row = 1 }))
  1012. eq({ all = 8, fill = 0 }, api.nvim_win_text_height(0, { start_row = 2, end_row = 2 }))
  1013. eq(
  1014. { all = 0, fill = 0 },
  1015. api.nvim_win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 0 })
  1016. )
  1017. eq(
  1018. { all = 1, fill = 0 },
  1019. api.nvim_win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 41 })
  1020. )
  1021. eq(
  1022. { all = 2, fill = 0 },
  1023. api.nvim_win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 42 })
  1024. )
  1025. eq(
  1026. { all = 2, fill = 0 },
  1027. api.nvim_win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 86 })
  1028. )
  1029. eq(
  1030. { all = 3, fill = 0 },
  1031. api.nvim_win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 87 })
  1032. )
  1033. eq(
  1034. { all = 6, fill = 0 },
  1035. api.nvim_win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 266 })
  1036. )
  1037. eq(
  1038. { all = 7, fill = 0 },
  1039. api.nvim_win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 267 })
  1040. )
  1041. eq(
  1042. { all = 7, fill = 0 },
  1043. api.nvim_win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 311 })
  1044. )
  1045. eq(
  1046. { all = 7, fill = 0 },
  1047. api.nvim_win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 312 })
  1048. )
  1049. eq(
  1050. { all = 7, fill = 0 },
  1051. api.nvim_win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = X })
  1052. )
  1053. eq(
  1054. { all = 7, fill = 0 },
  1055. api.nvim_win_text_height(0, { start_row = 1, start_vcol = 40, end_row = 1, end_vcol = X })
  1056. )
  1057. eq(
  1058. { all = 6, fill = 0 },
  1059. api.nvim_win_text_height(0, { start_row = 1, start_vcol = 41, end_row = 1, end_vcol = X })
  1060. )
  1061. eq(
  1062. { all = 6, fill = 0 },
  1063. api.nvim_win_text_height(0, { start_row = 1, start_vcol = 85, end_row = 1, end_vcol = X })
  1064. )
  1065. eq(
  1066. { all = 5, fill = 0 },
  1067. api.nvim_win_text_height(0, { start_row = 1, start_vcol = 86, end_row = 1, end_vcol = X })
  1068. )
  1069. eq(
  1070. { all = 2, fill = 0 },
  1071. api.nvim_win_text_height(0, { start_row = 1, start_vcol = 265, end_row = 1, end_vcol = X })
  1072. )
  1073. eq(
  1074. { all = 1, fill = 0 },
  1075. api.nvim_win_text_height(0, { start_row = 1, start_vcol = 266, end_row = 1, end_vcol = X })
  1076. )
  1077. eq(
  1078. { all = 1, fill = 0 },
  1079. api.nvim_win_text_height(0, { start_row = 1, start_vcol = 310, end_row = 1, end_vcol = X })
  1080. )
  1081. eq(
  1082. { all = 0, fill = 0 },
  1083. api.nvim_win_text_height(0, { start_row = 1, start_vcol = 311, end_row = 1, end_vcol = X })
  1084. )
  1085. eq(
  1086. { all = 1, fill = 0 },
  1087. api.nvim_win_text_height(0, { start_row = 1, start_vcol = 86, end_row = 1, end_vcol = 131 })
  1088. )
  1089. eq(
  1090. { all = 1, fill = 0 },
  1091. api.nvim_win_text_height(
  1092. 0,
  1093. { start_row = 1, start_vcol = 221, end_row = 1, end_vcol = 266 }
  1094. )
  1095. )
  1096. eq({ all = 18, fill = 0 }, api.nvim_win_text_height(0, { start_row = 0, start_vcol = 131 }))
  1097. eq({ all = 19, fill = 0 }, api.nvim_win_text_height(0, { start_row = 0, start_vcol = 130 }))
  1098. eq({ all = 20, fill = 0 }, api.nvim_win_text_height(0, { end_row = 2, end_vcol = 311 }))
  1099. eq({ all = 21, fill = 0 }, api.nvim_win_text_height(0, { end_row = 2, end_vcol = 312 }))
  1100. eq(
  1101. { all = 17, fill = 0 },
  1102. api.nvim_win_text_height(
  1103. 0,
  1104. { start_row = 0, start_vcol = 131, end_row = 2, end_vcol = 311 }
  1105. )
  1106. )
  1107. eq(
  1108. { all = 19, fill = 0 },
  1109. api.nvim_win_text_height(
  1110. 0,
  1111. { start_row = 0, start_vcol = 130, end_row = 2, end_vcol = 312 }
  1112. )
  1113. )
  1114. eq({ all = 16, fill = 0 }, api.nvim_win_text_height(0, { start_row = 0, start_vcol = 221 }))
  1115. eq({ all = 17, fill = 0 }, api.nvim_win_text_height(0, { start_row = 0, start_vcol = 220 }))
  1116. eq({ all = 14, fill = 0 }, api.nvim_win_text_height(0, { end_row = 2, end_vcol = 41 }))
  1117. eq({ all = 15, fill = 0 }, api.nvim_win_text_height(0, { end_row = 2, end_vcol = 42 }))
  1118. eq(
  1119. { all = 9, fill = 0 },
  1120. api.nvim_win_text_height(0, { start_row = 0, start_vcol = 221, end_row = 2, end_vcol = 41 })
  1121. )
  1122. eq(
  1123. { all = 11, fill = 0 },
  1124. api.nvim_win_text_height(0, { start_row = 0, start_vcol = 220, end_row = 2, end_vcol = 42 })
  1125. )
  1126. end)
  1127. end)
  1128. describe('open_win', function()
  1129. it('disallowed in cmdwin if enter=true or buf=cmdwin_buf', function()
  1130. local new_buf = api.nvim_create_buf(true, true)
  1131. feed('q:')
  1132. eq(
  1133. 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
  1134. pcall_err(api.nvim_open_win, new_buf, true, {
  1135. relative = 'editor',
  1136. row = 5,
  1137. col = 5,
  1138. width = 5,
  1139. height = 5,
  1140. })
  1141. )
  1142. eq(
  1143. 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
  1144. pcall_err(api.nvim_open_win, 0, false, {
  1145. relative = 'editor',
  1146. row = 5,
  1147. col = 5,
  1148. width = 5,
  1149. height = 5,
  1150. })
  1151. )
  1152. matches(
  1153. 'E11: Invalid in command%-line window; <CR> executes, CTRL%-C quits$',
  1154. pcall_err(
  1155. exec_lua,
  1156. [[
  1157. local cmdwin_buf = vim.api.nvim_get_current_buf()
  1158. vim._with({buf = vim.api.nvim_create_buf(false, true)}, function()
  1159. vim.api.nvim_open_win(cmdwin_buf, false, {
  1160. relative='editor', row=5, col=5, width=5, height=5,
  1161. })
  1162. end)
  1163. ]]
  1164. )
  1165. )
  1166. eq(
  1167. new_buf,
  1168. api.nvim_win_get_buf(api.nvim_open_win(new_buf, false, {
  1169. relative = 'editor',
  1170. row = 5,
  1171. col = 5,
  1172. width = 5,
  1173. height = 5,
  1174. }))
  1175. )
  1176. end)
  1177. it('aborts if buffer is invalid', function()
  1178. local wins_before = api.nvim_list_wins()
  1179. eq(
  1180. 'Invalid buffer id: 1337',
  1181. pcall_err(api.nvim_open_win, 1337, false, {
  1182. relative = 'editor',
  1183. row = 5,
  1184. col = 5,
  1185. width = 5,
  1186. height = 5,
  1187. })
  1188. )
  1189. eq(wins_before, api.nvim_list_wins())
  1190. end)
  1191. describe('creates a split window above', function()
  1192. local function test_open_win_split_above(key, val)
  1193. local initial_win = api.nvim_get_current_win()
  1194. local win = api.nvim_open_win(0, true, {
  1195. [key] = val,
  1196. height = 10,
  1197. })
  1198. eq('', api.nvim_win_get_config(win).relative)
  1199. eq(10, api.nvim_win_get_height(win))
  1200. local layout = fn.winlayout()
  1201. eq({
  1202. 'col',
  1203. {
  1204. { 'leaf', win },
  1205. { 'leaf', initial_win },
  1206. },
  1207. }, layout)
  1208. end
  1209. it("with split = 'above'", function()
  1210. test_open_win_split_above('split', 'above')
  1211. end)
  1212. it("with vertical = false and 'nosplitbelow'", function()
  1213. api.nvim_set_option_value('splitbelow', false, {})
  1214. test_open_win_split_above('vertical', false)
  1215. end)
  1216. end)
  1217. describe('creates a split window below', function()
  1218. local function test_open_win_split_below(key, val)
  1219. local initial_win = api.nvim_get_current_win()
  1220. local win = api.nvim_open_win(0, true, {
  1221. [key] = val,
  1222. height = 15,
  1223. })
  1224. eq('', api.nvim_win_get_config(win).relative)
  1225. eq(15, api.nvim_win_get_height(win))
  1226. local layout = fn.winlayout()
  1227. eq({
  1228. 'col',
  1229. {
  1230. { 'leaf', initial_win },
  1231. { 'leaf', win },
  1232. },
  1233. }, layout)
  1234. end
  1235. it("with split = 'below'", function()
  1236. test_open_win_split_below('split', 'below')
  1237. end)
  1238. it("with vertical = false and 'splitbelow'", function()
  1239. api.nvim_set_option_value('splitbelow', true, {})
  1240. test_open_win_split_below('vertical', false)
  1241. end)
  1242. end)
  1243. describe('creates a split window to the left', function()
  1244. local function test_open_win_split_left(key, val)
  1245. local initial_win = api.nvim_get_current_win()
  1246. local win = api.nvim_open_win(0, true, {
  1247. [key] = val,
  1248. width = 25,
  1249. })
  1250. eq('', api.nvim_win_get_config(win).relative)
  1251. eq(25, api.nvim_win_get_width(win))
  1252. local layout = fn.winlayout()
  1253. eq({
  1254. 'row',
  1255. {
  1256. { 'leaf', win },
  1257. { 'leaf', initial_win },
  1258. },
  1259. }, layout)
  1260. end
  1261. it("with split = 'left'", function()
  1262. test_open_win_split_left('split', 'left')
  1263. end)
  1264. it("with vertical = true and 'nosplitright'", function()
  1265. api.nvim_set_option_value('splitright', false, {})
  1266. test_open_win_split_left('vertical', true)
  1267. end)
  1268. end)
  1269. describe('creates a split window to the right', function()
  1270. local function test_open_win_split_right(key, val)
  1271. local initial_win = api.nvim_get_current_win()
  1272. local win = api.nvim_open_win(0, true, {
  1273. [key] = val,
  1274. width = 30,
  1275. })
  1276. eq('', api.nvim_win_get_config(win).relative)
  1277. eq(30, api.nvim_win_get_width(win))
  1278. local layout = fn.winlayout()
  1279. eq({
  1280. 'row',
  1281. {
  1282. { 'leaf', initial_win },
  1283. { 'leaf', win },
  1284. },
  1285. }, layout)
  1286. end
  1287. it("with split = 'right'", function()
  1288. test_open_win_split_right('split', 'right')
  1289. end)
  1290. it("with vertical = true and 'splitright'", function()
  1291. api.nvim_set_option_value('splitright', true, {})
  1292. test_open_win_split_right('vertical', true)
  1293. end)
  1294. end)
  1295. it("doesn't change tp_curwin when splitting window in another tab with enter=false", function()
  1296. local tab1 = api.nvim_get_current_tabpage()
  1297. local tab1_win = api.nvim_get_current_win()
  1298. n.command('tabnew')
  1299. local tab2 = api.nvim_get_current_tabpage()
  1300. local tab2_win = api.nvim_get_current_win()
  1301. eq({ tab1_win, tab2_win }, api.nvim_list_wins())
  1302. eq({ tab1, tab2 }, api.nvim_list_tabpages())
  1303. api.nvim_set_current_tabpage(tab1)
  1304. eq(tab1_win, api.nvim_get_current_win())
  1305. local tab2_prevwin = fn.tabpagewinnr(tab2, '#')
  1306. -- split in tab2 whine in tab2, with enter = false
  1307. local tab2_win2 = api.nvim_open_win(api.nvim_create_buf(false, true), false, {
  1308. win = tab2_win,
  1309. split = 'right',
  1310. })
  1311. eq(tab1_win, api.nvim_get_current_win()) -- we should still be in the first tp
  1312. eq(tab1_win, api.nvim_tabpage_get_win(tab1))
  1313. eq(tab2_win, api.nvim_tabpage_get_win(tab2)) -- tab2's tp_curwin should not have changed
  1314. eq(tab2_prevwin, fn.tabpagewinnr(tab2, '#')) -- tab2's tp_prevwin should not have changed
  1315. eq({ tab1_win, tab2_win, tab2_win2 }, api.nvim_list_wins())
  1316. eq({ tab2_win, tab2_win2 }, api.nvim_tabpage_list_wins(tab2))
  1317. end)
  1318. it('creates splits in the correct location', function()
  1319. local first_win = api.nvim_get_current_win()
  1320. -- specifying window 0 should create a split next to the current window
  1321. local win = api.nvim_open_win(0, true, {
  1322. vertical = false,
  1323. })
  1324. local layout = fn.winlayout()
  1325. eq({
  1326. 'col',
  1327. {
  1328. { 'leaf', win },
  1329. { 'leaf', first_win },
  1330. },
  1331. }, layout)
  1332. -- not specifying a window should create a top-level split
  1333. local win2 = api.nvim_open_win(0, true, {
  1334. split = 'left',
  1335. win = -1,
  1336. })
  1337. layout = fn.winlayout()
  1338. eq({
  1339. 'row',
  1340. {
  1341. { 'leaf', win2 },
  1342. {
  1343. 'col',
  1344. {
  1345. { 'leaf', win },
  1346. { 'leaf', first_win },
  1347. },
  1348. },
  1349. },
  1350. }, layout)
  1351. -- specifying a window should create a split next to that window
  1352. local win3 = api.nvim_open_win(0, true, {
  1353. win = win,
  1354. vertical = false,
  1355. })
  1356. layout = fn.winlayout()
  1357. eq({
  1358. 'row',
  1359. {
  1360. { 'leaf', win2 },
  1361. {
  1362. 'col',
  1363. {
  1364. { 'leaf', win3 },
  1365. { 'leaf', win },
  1366. { 'leaf', first_win },
  1367. },
  1368. },
  1369. },
  1370. }, layout)
  1371. end)
  1372. it('opens floating windows in other tabpages', function()
  1373. local first_win = api.nvim_get_current_win()
  1374. local first_tab = api.nvim_get_current_tabpage()
  1375. command('tabnew')
  1376. local new_tab = api.nvim_get_current_tabpage()
  1377. local win = api.nvim_open_win(0, false, {
  1378. relative = 'win',
  1379. win = first_win,
  1380. width = 5,
  1381. height = 5,
  1382. row = 1,
  1383. col = 1,
  1384. })
  1385. eq(api.nvim_win_get_tabpage(win), first_tab)
  1386. eq(api.nvim_get_current_tabpage(), new_tab)
  1387. end)
  1388. it('switches to new windows in non-current tabpages when enter=true', function()
  1389. local first_win = api.nvim_get_current_win()
  1390. local first_tab = api.nvim_get_current_tabpage()
  1391. command('tabnew')
  1392. local win = api.nvim_open_win(0, true, {
  1393. relative = 'win',
  1394. win = first_win,
  1395. width = 5,
  1396. height = 5,
  1397. row = 1,
  1398. col = 1,
  1399. })
  1400. eq(api.nvim_win_get_tabpage(win), first_tab)
  1401. eq(api.nvim_get_current_tabpage(), first_tab)
  1402. end)
  1403. local function setup_tabbed_autocmd_test()
  1404. local info = {}
  1405. info.orig_buf = api.nvim_get_current_buf()
  1406. info.other_buf = api.nvim_create_buf(true, true)
  1407. info.tab1_curwin = api.nvim_get_current_win()
  1408. info.tab1 = api.nvim_get_current_tabpage()
  1409. command('tab split | split')
  1410. info.tab2_curwin = api.nvim_get_current_win()
  1411. info.tab2 = api.nvim_get_current_tabpage()
  1412. exec([=[
  1413. tabfirst
  1414. let result = []
  1415. autocmd TabEnter * let result += [["TabEnter", nvim_get_current_tabpage()]]
  1416. autocmd TabLeave * let result += [["TabLeave", nvim_get_current_tabpage()]]
  1417. autocmd WinEnter * let result += [["WinEnter", win_getid()]]
  1418. autocmd WinLeave * let result += [["WinLeave", win_getid()]]
  1419. autocmd WinNew * let result += [["WinNew", win_getid()]]
  1420. autocmd WinClosed * let result += [["WinClosed", str2nr(expand("<afile>"))]]
  1421. autocmd BufEnter * let result += [["BufEnter", win_getid(), bufnr()]]
  1422. autocmd BufLeave * let result += [["BufLeave", win_getid(), bufnr()]]
  1423. autocmd BufWinEnter * let result += [["BufWinEnter", win_getid(), bufnr()]]
  1424. autocmd BufWinLeave * let result += [["BufWinLeave", win_getid(), bufnr()]]
  1425. ]=])
  1426. return info
  1427. end
  1428. it('noautocmd option works', function()
  1429. local info = setup_tabbed_autocmd_test()
  1430. api.nvim_open_win(
  1431. info.other_buf,
  1432. true,
  1433. { split = 'left', win = info.tab2_curwin, noautocmd = true }
  1434. )
  1435. eq({}, eval('result'))
  1436. api.nvim_open_win(
  1437. info.orig_buf,
  1438. true,
  1439. { relative = 'editor', row = 0, col = 0, width = 10, height = 10, noautocmd = true }
  1440. )
  1441. eq({}, eval('result'))
  1442. end)
  1443. it('fires expected autocmds when creating splits without entering', function()
  1444. local info = setup_tabbed_autocmd_test()
  1445. -- For these, don't want BufWinEnter if visiting the same buffer, like :{s}buffer.
  1446. -- Same tabpage, same buffer.
  1447. local new_win = api.nvim_open_win(0, false, { split = 'left', win = info.tab1_curwin })
  1448. eq({
  1449. { 'WinNew', new_win },
  1450. }, eval('result'))
  1451. eq(info.tab1_curwin, api.nvim_get_current_win())
  1452. -- Other tabpage, same buffer.
  1453. command('let result = []')
  1454. new_win = api.nvim_open_win(0, false, { split = 'left', win = info.tab2_curwin })
  1455. eq({
  1456. { 'WinNew', new_win },
  1457. }, eval('result'))
  1458. eq(info.tab1_curwin, api.nvim_get_current_win())
  1459. -- Same tabpage, other buffer.
  1460. command('let result = []')
  1461. new_win = api.nvim_open_win(info.other_buf, false, { split = 'left', win = info.tab1_curwin })
  1462. eq({
  1463. { 'WinNew', new_win },
  1464. { 'BufWinEnter', new_win, info.other_buf },
  1465. }, eval('result'))
  1466. eq(info.tab1_curwin, api.nvim_get_current_win())
  1467. -- Other tabpage, other buffer.
  1468. command('let result = []')
  1469. new_win = api.nvim_open_win(info.other_buf, false, { split = 'left', win = info.tab2_curwin })
  1470. eq({
  1471. { 'WinNew', new_win },
  1472. { 'BufWinEnter', new_win, info.other_buf },
  1473. }, eval('result'))
  1474. eq(info.tab1_curwin, api.nvim_get_current_win())
  1475. end)
  1476. it('fires expected autocmds when creating and entering splits', function()
  1477. local info = setup_tabbed_autocmd_test()
  1478. -- Same tabpage, same buffer.
  1479. local new_win = api.nvim_open_win(0, true, { split = 'left', win = info.tab1_curwin })
  1480. eq({
  1481. { 'WinNew', new_win },
  1482. { 'WinLeave', info.tab1_curwin },
  1483. { 'WinEnter', new_win },
  1484. }, eval('result'))
  1485. -- Same tabpage, other buffer.
  1486. api.nvim_set_current_win(info.tab1_curwin)
  1487. command('let result = []')
  1488. new_win = api.nvim_open_win(info.other_buf, true, { split = 'left', win = info.tab1_curwin })
  1489. eq({
  1490. { 'WinNew', new_win },
  1491. { 'WinLeave', info.tab1_curwin },
  1492. { 'WinEnter', new_win },
  1493. { 'BufLeave', new_win, info.orig_buf },
  1494. { 'BufEnter', new_win, info.other_buf },
  1495. { 'BufWinEnter', new_win, info.other_buf },
  1496. }, eval('result'))
  1497. -- For these, the other tabpage's prevwin and curwin will change like we switched from its old
  1498. -- curwin to the new window, so the extra events near TabEnter reflect that.
  1499. -- Other tabpage, same buffer.
  1500. api.nvim_set_current_win(info.tab1_curwin)
  1501. command('let result = []')
  1502. new_win = api.nvim_open_win(0, true, { split = 'left', win = info.tab2_curwin })
  1503. eq({
  1504. { 'WinNew', new_win },
  1505. { 'WinLeave', info.tab1_curwin },
  1506. { 'TabLeave', info.tab1 },
  1507. { 'WinEnter', info.tab2_curwin },
  1508. { 'TabEnter', info.tab2 },
  1509. { 'WinLeave', info.tab2_curwin },
  1510. { 'WinEnter', new_win },
  1511. }, eval('result'))
  1512. -- Other tabpage, other buffer.
  1513. api.nvim_set_current_win(info.tab2_curwin)
  1514. api.nvim_set_current_win(info.tab1_curwin)
  1515. command('let result = []')
  1516. new_win = api.nvim_open_win(info.other_buf, true, { split = 'left', win = info.tab2_curwin })
  1517. eq({
  1518. { 'WinNew', new_win },
  1519. { 'WinLeave', info.tab1_curwin },
  1520. { 'TabLeave', info.tab1 },
  1521. { 'WinEnter', info.tab2_curwin },
  1522. { 'TabEnter', info.tab2 },
  1523. { 'WinLeave', info.tab2_curwin },
  1524. { 'WinEnter', new_win },
  1525. { 'BufLeave', new_win, info.orig_buf },
  1526. { 'BufEnter', new_win, info.other_buf },
  1527. { 'BufWinEnter', new_win, info.other_buf },
  1528. }, eval('result'))
  1529. -- Other tabpage, other buffer; but other tabpage's curwin has a new buffer active.
  1530. api.nvim_set_current_win(info.tab2_curwin)
  1531. local new_buf = api.nvim_create_buf(true, true)
  1532. api.nvim_set_current_buf(new_buf)
  1533. api.nvim_set_current_win(info.tab1_curwin)
  1534. command('let result = []')
  1535. new_win = api.nvim_open_win(info.other_buf, true, { split = 'left', win = info.tab2_curwin })
  1536. eq({
  1537. { 'WinNew', new_win },
  1538. { 'BufLeave', info.tab1_curwin, info.orig_buf },
  1539. { 'WinLeave', info.tab1_curwin },
  1540. { 'TabLeave', info.tab1 },
  1541. { 'WinEnter', info.tab2_curwin },
  1542. { 'TabEnter', info.tab2 },
  1543. { 'BufEnter', info.tab2_curwin, new_buf },
  1544. { 'WinLeave', info.tab2_curwin },
  1545. { 'WinEnter', new_win },
  1546. { 'BufLeave', new_win, new_buf },
  1547. { 'BufEnter', new_win, info.other_buf },
  1548. { 'BufWinEnter', new_win, info.other_buf },
  1549. }, eval('result'))
  1550. end)
  1551. it('OK when new window is moved to other tabpage by autocommands', function()
  1552. -- Use nvim_win_set_config in the autocommands, as other methods of moving a window to a
  1553. -- different tabpage (e.g: wincmd T) actually creates a new window.
  1554. local tab0 = api.nvim_get_current_tabpage()
  1555. local tab0_win = api.nvim_get_current_win()
  1556. command('tabnew')
  1557. local new_buf = api.nvim_create_buf(true, true)
  1558. local tab1 = api.nvim_get_current_tabpage()
  1559. local tab1_parent = api.nvim_get_current_win()
  1560. command(
  1561. 'tabfirst | autocmd WinNew * ++once call nvim_win_set_config(0, #{split: "left", win: '
  1562. .. tab1_parent
  1563. .. '})'
  1564. )
  1565. local new_win = api.nvim_open_win(new_buf, true, { split = 'left' })
  1566. eq(tab1, api.nvim_get_current_tabpage())
  1567. eq(new_win, api.nvim_get_current_win())
  1568. eq(new_buf, api.nvim_get_current_buf())
  1569. -- nvim_win_set_config called after entering. It doesn't follow a curwin that is moved to a
  1570. -- different tabpage, but instead moves to the win filling the space, which is tab0_win.
  1571. command(
  1572. 'tabfirst | autocmd WinEnter * ++once call nvim_win_set_config(0, #{split: "left", win: '
  1573. .. tab1_parent
  1574. .. '})'
  1575. )
  1576. new_win = api.nvim_open_win(new_buf, true, { split = 'left' })
  1577. eq(tab0, api.nvim_get_current_tabpage())
  1578. eq(tab0_win, api.nvim_get_current_win())
  1579. eq(tab1, api.nvim_win_get_tabpage(new_win))
  1580. eq(new_buf, api.nvim_win_get_buf(new_win))
  1581. command(
  1582. 'tabfirst | autocmd BufEnter * ++once call nvim_win_set_config(0, #{split: "left", win: '
  1583. .. tab1_parent
  1584. .. '})'
  1585. )
  1586. new_win = api.nvim_open_win(new_buf, true, { split = 'left' })
  1587. eq(tab0, api.nvim_get_current_tabpage())
  1588. eq(tab0_win, api.nvim_get_current_win())
  1589. eq(tab1, api.nvim_win_get_tabpage(new_win))
  1590. eq(new_buf, api.nvim_win_get_buf(new_win))
  1591. end)
  1592. it('does not fire BufWinEnter if win_set_buf fails', function()
  1593. exec([[
  1594. set nohidden modified
  1595. autocmd WinNew * ++once only!
  1596. let fired = v:false
  1597. autocmd BufWinEnter * ++once let fired = v:true
  1598. ]])
  1599. eq(
  1600. 'Vim:E37: No write since last change (add ! to override)',
  1601. pcall_err(api.nvim_open_win, api.nvim_create_buf(true, true), false, { split = 'left' })
  1602. )
  1603. eq(false, eval('fired'))
  1604. end)
  1605. it('fires Buf* autocommands when `!enter` if window is entered via autocommands', function()
  1606. exec([[
  1607. autocmd WinNew * ++once only!
  1608. let fired = v:false
  1609. autocmd BufEnter * ++once let fired = v:true
  1610. ]])
  1611. api.nvim_open_win(api.nvim_create_buf(true, true), false, { split = 'left' })
  1612. eq(true, eval('fired'))
  1613. end)
  1614. it('no heap-use-after-free if target buffer deleted by autocommands', function()
  1615. local cur_buf = api.nvim_get_current_buf()
  1616. local new_buf = api.nvim_create_buf(true, true)
  1617. command('autocmd WinNew * ++once call nvim_buf_delete(' .. new_buf .. ', #{force: 1})')
  1618. api.nvim_open_win(new_buf, true, { split = 'left' })
  1619. eq(cur_buf, api.nvim_get_current_buf())
  1620. end)
  1621. it('checks if splitting disallowed', function()
  1622. command('split | autocmd WinEnter * ++once call nvim_open_win(0, 0, #{split: "right"})')
  1623. matches("E242: Can't split a window while closing another$", pcall_err(command, 'quit'))
  1624. command('only | autocmd BufHidden * ++once call nvim_open_win(0, 0, #{split: "left"})')
  1625. matches(
  1626. 'E1159: Cannot split a window when closing the buffer$',
  1627. pcall_err(command, 'new | quit')
  1628. )
  1629. local w = api.nvim_get_current_win()
  1630. command(
  1631. 'only | new | autocmd BufHidden * ++once call nvim_open_win(0, 0, #{split: "left", win: '
  1632. .. w
  1633. .. '})'
  1634. )
  1635. matches(
  1636. 'E1159: Cannot split a window when closing the buffer$',
  1637. pcall_err(api.nvim_win_close, w, true)
  1638. )
  1639. -- OK when using window to different buffer than `win`s.
  1640. w = api.nvim_get_current_win()
  1641. command(
  1642. 'only | autocmd BufHidden * ++once call nvim_open_win(0, 0, #{split: "left", win: '
  1643. .. w
  1644. .. '})'
  1645. )
  1646. command('new | quit')
  1647. end)
  1648. it('restores last known cursor position if BufWinEnter did not move it', function()
  1649. -- This test mostly exists to ensure BufWinEnter is executed before enter_buffer's epilogue.
  1650. local buf = api.nvim_get_current_buf()
  1651. insert([[
  1652. foo
  1653. bar baz .etc
  1654. i love autocommand bugs!
  1655. supercalifragilisticexpialidocious
  1656. marvim is actually a human
  1657. llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch
  1658. ]])
  1659. api.nvim_win_set_cursor(0, { 5, 2 })
  1660. command('set nostartofline | enew')
  1661. local new_win = api.nvim_open_win(buf, false, { split = 'left' })
  1662. eq({ 5, 2 }, api.nvim_win_get_cursor(new_win))
  1663. exec([[
  1664. only!
  1665. autocmd BufWinEnter * ++once normal! j6l
  1666. ]])
  1667. new_win = api.nvim_open_win(buf, false, { split = 'left' })
  1668. eq({ 2, 6 }, api.nvim_win_get_cursor(new_win))
  1669. end)
  1670. it('does not block all win_set_buf autocommands if !enter and !noautocmd', function()
  1671. local new_buf = fn.bufadd('foobarbaz')
  1672. exec([[
  1673. let triggered = ""
  1674. autocmd BufReadCmd * ++once let triggered = bufname()
  1675. ]])
  1676. api.nvim_open_win(new_buf, false, { split = 'left' })
  1677. eq('foobarbaz', eval('triggered'))
  1678. end)
  1679. it('sets error when no room', function()
  1680. matches('E36: Not enough room$', pcall_err(command, 'execute "split|"->repeat(&lines)'))
  1681. matches(
  1682. 'E36: Not enough room$',
  1683. pcall_err(api.nvim_open_win, 0, true, { split = 'above', win = 0 })
  1684. )
  1685. matches(
  1686. 'E36: Not enough room$',
  1687. pcall_err(api.nvim_open_win, 0, true, { split = 'below', win = 0 })
  1688. )
  1689. end)
  1690. describe("with 'autochdir'", function()
  1691. local topdir
  1692. local otherbuf
  1693. before_each(function()
  1694. command('set shellslash')
  1695. topdir = fn.getcwd()
  1696. t.mkdir(topdir .. '/Xacd')
  1697. t.mkdir(topdir .. '/Xacd/foo')
  1698. otherbuf = api.nvim_create_buf(false, true)
  1699. api.nvim_buf_set_name(otherbuf, topdir .. '/Xacd/baz.txt')
  1700. command('set autochdir')
  1701. command('edit Xacd/foo/bar.txt')
  1702. eq(topdir .. '/Xacd/foo', fn.getcwd())
  1703. end)
  1704. after_each(function()
  1705. n.rmdir(topdir .. '/Xacd')
  1706. end)
  1707. it('does not change cwd with enter=false #15280', function()
  1708. api.nvim_open_win(
  1709. otherbuf,
  1710. false,
  1711. { relative = 'editor', height = 5, width = 5, row = 5, col = 5 }
  1712. )
  1713. eq(topdir .. '/Xacd/foo', fn.getcwd())
  1714. end)
  1715. it('changes cwd with enter=true', function()
  1716. api.nvim_open_win(
  1717. otherbuf,
  1718. true,
  1719. { relative = 'editor', height = 5, width = 5, row = 5, col = 5 }
  1720. )
  1721. eq(topdir .. '/Xacd', fn.getcwd())
  1722. end)
  1723. end)
  1724. it('no memory leak with valid title and invalid footer', function()
  1725. eq(
  1726. 'title/footer must be string or array',
  1727. pcall_err(api.nvim_open_win, 0, false, {
  1728. relative = 'editor',
  1729. row = 10,
  1730. col = 10,
  1731. height = 10,
  1732. width = 10,
  1733. border = 'single',
  1734. title = { { 'TITLE' } },
  1735. footer = 0,
  1736. })
  1737. )
  1738. end)
  1739. it('no memory leak with invalid title and valid footer', function()
  1740. eq(
  1741. 'title/footer must be string or array',
  1742. pcall_err(api.nvim_open_win, 0, false, {
  1743. relative = 'editor',
  1744. row = 10,
  1745. col = 10,
  1746. height = 10,
  1747. width = 10,
  1748. border = 'single',
  1749. title = 0,
  1750. footer = { { 'FOOTER' } },
  1751. })
  1752. )
  1753. end)
  1754. end)
  1755. describe('set_config', function()
  1756. it('moves a split into a float', function()
  1757. local win = api.nvim_open_win(0, true, {
  1758. vertical = false,
  1759. })
  1760. eq('', api.nvim_win_get_config(win).relative)
  1761. api.nvim_win_set_config(win, {
  1762. relative = 'editor',
  1763. row = 5,
  1764. col = 5,
  1765. width = 5,
  1766. height = 5,
  1767. })
  1768. eq('editor', api.nvim_win_get_config(win).relative)
  1769. end)
  1770. it('throws error when attempting to move the last window', function()
  1771. local err = pcall_err(api.nvim_win_set_config, 0, {
  1772. vertical = false,
  1773. })
  1774. eq('Cannot move last window', err)
  1775. end)
  1776. it('passing retval of get_config results in no-op', function()
  1777. -- simple split layout
  1778. local win = api.nvim_open_win(0, true, {
  1779. split = 'left',
  1780. })
  1781. local layout = fn.winlayout()
  1782. local config = api.nvim_win_get_config(win)
  1783. api.nvim_win_set_config(win, config)
  1784. eq(layout, fn.winlayout())
  1785. -- nested split layout
  1786. local win2 = api.nvim_open_win(0, true, {
  1787. vertical = true,
  1788. })
  1789. local win3 = api.nvim_open_win(0, true, {
  1790. win = win2,
  1791. vertical = false,
  1792. })
  1793. layout = fn.winlayout()
  1794. config = api.nvim_win_get_config(win2)
  1795. api.nvim_win_set_config(win2, config)
  1796. eq(layout, fn.winlayout())
  1797. config = api.nvim_win_get_config(win3)
  1798. api.nvim_win_set_config(win3, config)
  1799. eq(layout, fn.winlayout())
  1800. end)
  1801. it('moves a float into a split', function()
  1802. local layout = fn.winlayout()
  1803. eq('leaf', layout[1])
  1804. local win = api.nvim_open_win(0, true, {
  1805. relative = 'editor',
  1806. row = 5,
  1807. col = 5,
  1808. width = 5,
  1809. height = 5,
  1810. })
  1811. api.nvim_win_set_config(win, {
  1812. split = 'below',
  1813. win = -1,
  1814. })
  1815. eq('', api.nvim_win_get_config(win).relative)
  1816. layout = fn.winlayout()
  1817. eq('col', layout[1])
  1818. eq(2, #layout[2])
  1819. eq(win, layout[2][2][2])
  1820. end)
  1821. it('respects the "split" option', function()
  1822. local layout = fn.winlayout()
  1823. eq('leaf', layout[1])
  1824. local first_win = layout[2]
  1825. local win = api.nvim_open_win(0, true, {
  1826. relative = 'editor',
  1827. row = 5,
  1828. col = 5,
  1829. width = 5,
  1830. height = 5,
  1831. })
  1832. api.nvim_win_set_config(win, {
  1833. split = 'right',
  1834. win = first_win,
  1835. })
  1836. layout = fn.winlayout()
  1837. eq('row', layout[1])
  1838. eq(2, #layout[2])
  1839. eq(win, layout[2][2][2])
  1840. local config = api.nvim_win_get_config(win)
  1841. eq('', config.relative)
  1842. eq('right', config.split)
  1843. api.nvim_win_set_config(win, {
  1844. split = 'below',
  1845. win = first_win,
  1846. })
  1847. layout = fn.winlayout()
  1848. eq('col', layout[1])
  1849. eq(2, #layout[2])
  1850. eq(win, layout[2][2][2])
  1851. config = api.nvim_win_get_config(win)
  1852. eq('', config.relative)
  1853. eq('below', config.split)
  1854. eq(
  1855. "non-float with 'win' requires at least 'split' or 'vertical'",
  1856. pcall_err(api.nvim_win_set_config, 0, { win = 0 })
  1857. )
  1858. eq(
  1859. "non-float with 'win' requires at least 'split' or 'vertical'",
  1860. pcall_err(api.nvim_win_set_config, 0, { win = 0, relative = '' })
  1861. )
  1862. end)
  1863. it('creates top-level splits', function()
  1864. local win = api.nvim_open_win(0, true, {
  1865. vertical = false,
  1866. })
  1867. local win2 = api.nvim_open_win(0, true, {
  1868. vertical = true,
  1869. win = -1,
  1870. })
  1871. local layout = fn.winlayout()
  1872. eq('row', layout[1])
  1873. eq(2, #layout[2])
  1874. eq(win2, layout[2][1][2])
  1875. api.nvim_win_set_config(win, {
  1876. split = 'below',
  1877. win = -1,
  1878. })
  1879. layout = fn.winlayout()
  1880. eq('col', layout[1])
  1881. eq(2, #layout[2])
  1882. eq('row', layout[2][1][1])
  1883. eq(win, layout[2][2][2])
  1884. end)
  1885. it('moves splits to other tabpages', function()
  1886. local curtab = api.nvim_get_current_tabpage()
  1887. local win = api.nvim_open_win(0, false, { split = 'left' })
  1888. command('tabnew')
  1889. local tabnr = api.nvim_get_current_tabpage()
  1890. command('tabprev') -- return to the initial tab
  1891. api.nvim_win_set_config(win, {
  1892. split = 'right',
  1893. win = api.nvim_tabpage_get_win(tabnr),
  1894. })
  1895. eq(tabnr, api.nvim_win_get_tabpage(win))
  1896. -- we are changing the config, the current tabpage should not change
  1897. eq(curtab, api.nvim_get_current_tabpage())
  1898. command('tabnext') -- switch to the new tabpage so we can get the layout
  1899. local layout = fn.winlayout()
  1900. eq({
  1901. 'row',
  1902. {
  1903. { 'leaf', api.nvim_tabpage_get_win(tabnr) },
  1904. { 'leaf', win },
  1905. },
  1906. }, layout)
  1907. end)
  1908. it('correctly moves curwin when moving curwin to a different tabpage', function()
  1909. local curtab = api.nvim_get_current_tabpage()
  1910. command('tabnew')
  1911. local tab2 = api.nvim_get_current_tabpage()
  1912. local tab2_win = api.nvim_get_current_win()
  1913. command('tabprev') -- return to the initial tab
  1914. local neighbor = api.nvim_get_current_win()
  1915. -- create and enter a new split
  1916. local win = api.nvim_open_win(0, true, {
  1917. vertical = false,
  1918. })
  1919. eq(curtab, api.nvim_win_get_tabpage(win))
  1920. eq({ win, neighbor }, api.nvim_tabpage_list_wins(curtab))
  1921. -- move the current win to a different tabpage
  1922. api.nvim_win_set_config(win, {
  1923. split = 'right',
  1924. win = api.nvim_tabpage_get_win(tab2),
  1925. })
  1926. eq(curtab, api.nvim_get_current_tabpage())
  1927. -- win should have moved to tab2
  1928. eq(tab2, api.nvim_win_get_tabpage(win))
  1929. -- tp_curwin of tab2 should not have changed
  1930. eq(tab2_win, api.nvim_tabpage_get_win(tab2))
  1931. -- win lists should be correct
  1932. eq({ tab2_win, win }, api.nvim_tabpage_list_wins(tab2))
  1933. eq({ neighbor }, api.nvim_tabpage_list_wins(curtab))
  1934. -- current win should have moved to neighboring win
  1935. eq(neighbor, api.nvim_tabpage_get_win(curtab))
  1936. end)
  1937. it('splits windows in non-current tabpage', function()
  1938. local curtab = api.nvim_get_current_tabpage()
  1939. command('tabnew')
  1940. local tabnr = api.nvim_get_current_tabpage()
  1941. command('tabprev') -- return to the initial tab
  1942. local win = api.nvim_open_win(0, false, {
  1943. vertical = false,
  1944. win = api.nvim_tabpage_get_win(tabnr),
  1945. })
  1946. eq(tabnr, api.nvim_win_get_tabpage(win))
  1947. -- since enter = false, the current tabpage should not change
  1948. eq(curtab, api.nvim_get_current_tabpage())
  1949. end)
  1950. it('moves the current split window', function()
  1951. local initial_win = api.nvim_get_current_win()
  1952. local win = api.nvim_open_win(0, true, {
  1953. vertical = true,
  1954. })
  1955. local win2 = api.nvim_open_win(0, true, {
  1956. vertical = true,
  1957. })
  1958. api.nvim_set_current_win(win)
  1959. eq({
  1960. 'row',
  1961. {
  1962. { 'leaf', win2 },
  1963. { 'leaf', win },
  1964. { 'leaf', initial_win },
  1965. },
  1966. }, fn.winlayout())
  1967. api.nvim_win_set_config(0, {
  1968. vertical = false,
  1969. win = 0,
  1970. })
  1971. eq(win, api.nvim_get_current_win())
  1972. eq({
  1973. 'col',
  1974. {
  1975. { 'leaf', win },
  1976. {
  1977. 'row',
  1978. {
  1979. { 'leaf', win2 },
  1980. { 'leaf', initial_win },
  1981. },
  1982. },
  1983. },
  1984. }, fn.winlayout())
  1985. api.nvim_set_current_win(win2)
  1986. local win3 = api.nvim_open_win(0, true, {
  1987. vertical = true,
  1988. })
  1989. eq(win3, api.nvim_get_current_win())
  1990. eq({
  1991. 'col',
  1992. {
  1993. { 'leaf', win },
  1994. {
  1995. 'row',
  1996. {
  1997. { 'leaf', win3 },
  1998. { 'leaf', win2 },
  1999. { 'leaf', initial_win },
  2000. },
  2001. },
  2002. },
  2003. }, fn.winlayout())
  2004. api.nvim_win_set_config(0, {
  2005. vertical = false,
  2006. win = 0,
  2007. })
  2008. eq(win3, api.nvim_get_current_win())
  2009. eq({
  2010. 'col',
  2011. {
  2012. { 'leaf', win },
  2013. {
  2014. 'row',
  2015. {
  2016. {
  2017. 'col',
  2018. {
  2019. { 'leaf', win3 },
  2020. { 'leaf', win2 },
  2021. },
  2022. },
  2023. { 'leaf', initial_win },
  2024. },
  2025. },
  2026. },
  2027. }, fn.winlayout())
  2028. end)
  2029. it('closing new curwin when moving window to other tabpage works', function()
  2030. command('split | tabnew')
  2031. local t2_win = api.nvim_get_current_win()
  2032. command('tabfirst | autocmd WinEnter * ++once quit')
  2033. local t1_move_win = api.nvim_get_current_win()
  2034. -- win_set_config fails to switch away from "t1_move_win" because the WinEnter autocmd that
  2035. -- closed the window we're switched to returns us to "t1_move_win", as it filled the space.
  2036. eq(
  2037. 'Failed to switch away from window ' .. t1_move_win,
  2038. pcall_err(api.nvim_win_set_config, t1_move_win, { win = t2_win, split = 'left' })
  2039. )
  2040. eq(t1_move_win, api.nvim_get_current_win())
  2041. command('split | split | autocmd WinEnter * ++once quit')
  2042. t1_move_win = api.nvim_get_current_win()
  2043. -- In this case, we closed the window that we got switched to, but doing so didn't switch us
  2044. -- back to "t1_move_win", which is fine.
  2045. api.nvim_win_set_config(t1_move_win, { win = t2_win, split = 'left' })
  2046. neq(t1_move_win, api.nvim_get_current_win())
  2047. end)
  2048. it('messing with "win" or "parent" when moving "win" to other tabpage', function()
  2049. command('split | tabnew')
  2050. local t2 = api.nvim_get_current_tabpage()
  2051. local t2_win1 = api.nvim_get_current_win()
  2052. command('split')
  2053. local t2_win2 = api.nvim_get_current_win()
  2054. command('split')
  2055. local t2_win3 = api.nvim_get_current_win()
  2056. command('tabfirst | autocmd WinEnter * ++once call nvim_win_close(' .. t2_win1 .. ', 1)')
  2057. local cur_win = api.nvim_get_current_win()
  2058. eq(
  2059. 'Windows to split were closed',
  2060. pcall_err(api.nvim_win_set_config, 0, { win = t2_win1, split = 'left' })
  2061. )
  2062. eq(cur_win, api.nvim_get_current_win())
  2063. command('split | autocmd WinLeave * ++once quit!')
  2064. cur_win = api.nvim_get_current_win()
  2065. eq(
  2066. 'Windows to split were closed',
  2067. pcall_err(api.nvim_win_set_config, 0, { win = t2_win2, split = 'left' })
  2068. )
  2069. neq(cur_win, api.nvim_get_current_win())
  2070. exec([[
  2071. split
  2072. autocmd WinLeave * ++once
  2073. \ call nvim_win_set_config(0, #{relative:'editor', row:0, col:0, width:5, height:5})
  2074. ]])
  2075. cur_win = api.nvim_get_current_win()
  2076. eq(
  2077. 'Floating state of windows to split changed',
  2078. pcall_err(api.nvim_win_set_config, 0, { win = t2_win3, split = 'left' })
  2079. )
  2080. eq('editor', api.nvim_win_get_config(0).relative)
  2081. eq(cur_win, api.nvim_get_current_win())
  2082. command('autocmd WinLeave * ++once wincmd J')
  2083. cur_win = api.nvim_get_current_win()
  2084. eq(
  2085. 'Floating state of windows to split changed',
  2086. pcall_err(api.nvim_win_set_config, 0, { win = t2_win3, split = 'left' })
  2087. )
  2088. eq('', api.nvim_win_get_config(0).relative)
  2089. eq(cur_win, api.nvim_get_current_win())
  2090. -- Try to make "parent" floating. This should give the same error as before, but because
  2091. -- changing a split from another tabpage into a float isn't supported yet, check for that
  2092. -- error instead for now.
  2093. -- Use ":silent!" to avoid the one second delay from printing the error message.
  2094. exec(([[
  2095. autocmd WinLeave * ++once silent!
  2096. \ call nvim_win_set_config(%d, #{relative:'editor', row:0, col:0, width:5, height:5})
  2097. ]]):format(t2_win3))
  2098. cur_win = api.nvim_get_current_win()
  2099. api.nvim_win_set_config(0, { win = t2_win3, split = 'left' })
  2100. matches(
  2101. 'Cannot change window from different tabpage into float$',
  2102. api.nvim_get_vvar('errmsg')
  2103. )
  2104. -- The error doesn't abort moving the window (or maybe it should, if that's wanted?)
  2105. neq(cur_win, api.nvim_get_current_win())
  2106. eq(t2, api.nvim_win_get_tabpage(cur_win))
  2107. end)
  2108. it('expected autocmds when moving window to other tabpage', function()
  2109. local new_curwin = api.nvim_get_current_win()
  2110. command('split')
  2111. local win = api.nvim_get_current_win()
  2112. command('tabnew')
  2113. local parent = api.nvim_get_current_win()
  2114. exec([[
  2115. tabfirst
  2116. let result = []
  2117. autocmd WinEnter * let result += ["Enter", win_getid()]
  2118. autocmd WinLeave * let result += ["Leave", win_getid()]
  2119. autocmd WinNew * let result += ["New", win_getid()]
  2120. ]])
  2121. api.nvim_win_set_config(0, { win = parent, split = 'left' })
  2122. -- Shouldn't see WinNew, as we're not creating any new windows, just moving existing ones.
  2123. eq({ 'Leave', win, 'Enter', new_curwin }, eval('result'))
  2124. end)
  2125. it('no autocmds when moving window within same tabpage', function()
  2126. local parent = api.nvim_get_current_win()
  2127. exec([[
  2128. split
  2129. let result = []
  2130. autocmd WinEnter * let result += ["Enter", win_getid()]
  2131. autocmd WinLeave * let result += ["Leave", win_getid()]
  2132. autocmd WinNew * let result += ["New", win_getid()]
  2133. ]])
  2134. api.nvim_win_set_config(0, { win = parent, split = 'left' })
  2135. -- Shouldn't see any of those events, as we remain in the same window.
  2136. eq({}, eval('result'))
  2137. end)
  2138. it('checks if splitting disallowed', function()
  2139. command('split | autocmd WinEnter * ++once call nvim_win_set_config(0, #{split: "right"})')
  2140. matches("E242: Can't split a window while closing another$", pcall_err(command, 'quit'))
  2141. command('autocmd BufHidden * ++once call nvim_win_set_config(0, #{split: "left"})')
  2142. matches(
  2143. 'E1159: Cannot split a window when closing the buffer$',
  2144. pcall_err(command, 'new | quit')
  2145. )
  2146. -- OK when using window to different buffer.
  2147. local w = api.nvim_get_current_win()
  2148. command('autocmd BufHidden * ++once call nvim_win_set_config(' .. w .. ', #{split: "left"})')
  2149. command('new | quit')
  2150. end)
  2151. --- Returns a function to get information about the window layout, sizes and positions of a
  2152. --- tabpage.
  2153. local function define_tp_info_function()
  2154. exec_lua([[
  2155. function tp_info(tp)
  2156. return {
  2157. layout = vim.fn.winlayout(vim.api.nvim_tabpage_get_number(tp)),
  2158. pos_sizes = vim.tbl_map(
  2159. function(w)
  2160. local pos = vim.fn.win_screenpos(w)
  2161. return {
  2162. row = pos[1],
  2163. col = pos[2],
  2164. width = vim.fn.winwidth(w),
  2165. height = vim.fn.winheight(w)
  2166. }
  2167. end,
  2168. vim.api.nvim_tabpage_list_wins(tp)
  2169. )
  2170. }
  2171. end
  2172. ]])
  2173. return function(tp)
  2174. return exec_lua('return tp_info(...)', tp)
  2175. end
  2176. end
  2177. it('attempt to move window with no room', function()
  2178. -- Fill the 2nd tabpage full of windows until we run out of room.
  2179. -- Use &laststatus=0 to ensure restoring missing statuslines doesn't affect things.
  2180. command('set laststatus=0 | tabnew')
  2181. matches('E36: Not enough room$', pcall_err(command, 'execute "split|"->repeat(&lines)'))
  2182. command('vsplit | wincmd | | wincmd p')
  2183. local t2 = api.nvim_get_current_tabpage()
  2184. local t2_cur_win = api.nvim_get_current_win()
  2185. local t2_top_split = fn.win_getid(1)
  2186. local t2_bot_split = fn.win_getid(fn.winnr('$'))
  2187. local t2_float = api.nvim_open_win(
  2188. 0,
  2189. false,
  2190. { relative = 'editor', row = 0, col = 0, width = 10, height = 10 }
  2191. )
  2192. local t2_float_config = api.nvim_win_get_config(t2_float)
  2193. local tp_info = define_tp_info_function()
  2194. local t2_info = tp_info(t2)
  2195. matches(
  2196. 'E36: Not enough room$',
  2197. pcall_err(api.nvim_win_set_config, 0, { win = t2_top_split, split = 'above' })
  2198. )
  2199. matches(
  2200. 'E36: Not enough room$',
  2201. pcall_err(api.nvim_win_set_config, 0, { win = t2_top_split, split = 'below' })
  2202. )
  2203. matches(
  2204. 'E36: Not enough room$',
  2205. pcall_err(api.nvim_win_set_config, 0, { win = t2_bot_split, split = 'above' })
  2206. )
  2207. matches(
  2208. 'E36: Not enough room$',
  2209. pcall_err(api.nvim_win_set_config, 0, { win = t2_bot_split, split = 'below' })
  2210. )
  2211. matches(
  2212. 'E36: Not enough room$',
  2213. pcall_err(api.nvim_win_set_config, t2_float, { win = t2_top_split, split = 'above' })
  2214. )
  2215. matches(
  2216. 'E36: Not enough room$',
  2217. pcall_err(api.nvim_win_set_config, t2_float, { win = t2_top_split, split = 'below' })
  2218. )
  2219. matches(
  2220. 'E36: Not enough room$',
  2221. pcall_err(api.nvim_win_set_config, t2_float, { win = t2_bot_split, split = 'above' })
  2222. )
  2223. matches(
  2224. 'E36: Not enough room$',
  2225. pcall_err(api.nvim_win_set_config, t2_float, { win = t2_bot_split, split = 'below' })
  2226. )
  2227. eq(t2_cur_win, api.nvim_get_current_win())
  2228. eq(t2_info, tp_info(t2))
  2229. eq(t2_float_config, api.nvim_win_get_config(t2_float))
  2230. -- Try to move windows from the 1st tabpage to the 2nd.
  2231. command('tabfirst | split | wincmd _')
  2232. local t1 = api.nvim_get_current_tabpage()
  2233. local t1_cur_win = api.nvim_get_current_win()
  2234. local t1_float = api.nvim_open_win(
  2235. 0,
  2236. false,
  2237. { relative = 'editor', row = 5, col = 3, width = 7, height = 6 }
  2238. )
  2239. local t1_float_config = api.nvim_win_get_config(t1_float)
  2240. local t1_info = tp_info(t1)
  2241. matches(
  2242. 'E36: Not enough room$',
  2243. pcall_err(api.nvim_win_set_config, 0, { win = t2_top_split, split = 'above' })
  2244. )
  2245. matches(
  2246. 'E36: Not enough room$',
  2247. pcall_err(api.nvim_win_set_config, 0, { win = t2_top_split, split = 'below' })
  2248. )
  2249. matches(
  2250. 'E36: Not enough room$',
  2251. pcall_err(api.nvim_win_set_config, 0, { win = t2_bot_split, split = 'above' })
  2252. )
  2253. matches(
  2254. 'E36: Not enough room$',
  2255. pcall_err(api.nvim_win_set_config, 0, { win = t2_bot_split, split = 'below' })
  2256. )
  2257. matches(
  2258. 'E36: Not enough room$',
  2259. pcall_err(api.nvim_win_set_config, t1_float, { win = t2_top_split, split = 'above' })
  2260. )
  2261. matches(
  2262. 'E36: Not enough room$',
  2263. pcall_err(api.nvim_win_set_config, t1_float, { win = t2_top_split, split = 'below' })
  2264. )
  2265. matches(
  2266. 'E36: Not enough room$',
  2267. pcall_err(api.nvim_win_set_config, t1_float, { win = t2_bot_split, split = 'above' })
  2268. )
  2269. matches(
  2270. 'E36: Not enough room$',
  2271. pcall_err(api.nvim_win_set_config, t1_float, { win = t2_bot_split, split = 'below' })
  2272. )
  2273. eq(t1_cur_win, api.nvim_get_current_win())
  2274. eq(t1_info, tp_info(t1))
  2275. eq(t1_float_config, api.nvim_win_get_config(t1_float))
  2276. end)
  2277. it('attempt to move window from other tabpage with no room', function()
  2278. -- Fill up the 1st tabpage with horizontal splits, then create a 2nd with only a few. Go back
  2279. -- to the 1st and try to move windows from the 2nd (while it's non-current) to it. Check that
  2280. -- window positions and sizes in the 2nd are unchanged.
  2281. command('set laststatus=0')
  2282. matches('E36: Not enough room$', pcall_err(command, 'execute "split|"->repeat(&lines)'))
  2283. command('tab split')
  2284. local t2 = api.nvim_get_current_tabpage()
  2285. local t2_top = api.nvim_get_current_win()
  2286. command('belowright split')
  2287. local t2_mid_left = api.nvim_get_current_win()
  2288. command('belowright vsplit')
  2289. local t2_mid_right = api.nvim_get_current_win()
  2290. command('split | wincmd J')
  2291. local t2_bot = api.nvim_get_current_win()
  2292. local tp_info = define_tp_info_function()
  2293. local t2_info = tp_info(t2)
  2294. eq({
  2295. 'col',
  2296. {
  2297. { 'leaf', t2_top },
  2298. {
  2299. 'row',
  2300. {
  2301. { 'leaf', t2_mid_left },
  2302. { 'leaf', t2_mid_right },
  2303. },
  2304. },
  2305. { 'leaf', t2_bot },
  2306. },
  2307. }, t2_info.layout)
  2308. local function try_move_t2_wins_to_t1()
  2309. for _, w in ipairs({ t2_bot, t2_mid_left, t2_mid_right, t2_top }) do
  2310. matches(
  2311. 'E36: Not enough room$',
  2312. pcall_err(api.nvim_win_set_config, w, { win = 0, split = 'below' })
  2313. )
  2314. eq(t2_info, tp_info(t2))
  2315. end
  2316. end
  2317. command('tabfirst')
  2318. try_move_t2_wins_to_t1()
  2319. -- Go to the 2nd tabpage to ensure nothing changes after win_comp_pos, last_status, .etc.
  2320. -- from enter_tabpage.
  2321. command('tabnext')
  2322. eq(t2_info, tp_info(t2))
  2323. -- Check things are fine with the global statusline too, for good measure.
  2324. -- Set it while the 2nd tabpage is current, so last_status runs for it.
  2325. command('set laststatus=3')
  2326. t2_info = tp_info(t2)
  2327. command('tabfirst')
  2328. try_move_t2_wins_to_t1()
  2329. end)
  2330. it('handles cmdwin and textlock restrictions', function()
  2331. command('tabnew')
  2332. local t2 = api.nvim_get_current_tabpage()
  2333. local t2_win = api.nvim_get_current_win()
  2334. command('tabfirst')
  2335. local t1_move_win = api.nvim_get_current_win()
  2336. command('split')
  2337. -- Can't move the cmdwin, or its old curwin to a different tabpage.
  2338. local old_curwin = api.nvim_get_current_win()
  2339. feed('q:')
  2340. eq(
  2341. 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
  2342. pcall_err(api.nvim_win_set_config, 0, { split = 'left', win = t2_win })
  2343. )
  2344. eq(
  2345. 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
  2346. pcall_err(api.nvim_win_set_config, old_curwin, { split = 'left', win = t2_win })
  2347. )
  2348. -- But we can move other windows.
  2349. api.nvim_win_set_config(t1_move_win, { split = 'left', win = t2_win })
  2350. eq(t2, api.nvim_win_get_tabpage(t1_move_win))
  2351. command('quit!')
  2352. -- Can't configure windows such that the cmdwin would become the only non-float.
  2353. command('only!')
  2354. feed('q:')
  2355. eq(
  2356. 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
  2357. pcall_err(
  2358. api.nvim_win_set_config,
  2359. old_curwin,
  2360. { relative = 'editor', row = 0, col = 0, width = 5, height = 5 }
  2361. )
  2362. )
  2363. -- old_curwin is now no longer the only other non-float, so we can make it floating now.
  2364. local t1_new_win = api.nvim_open_win(
  2365. api.nvim_create_buf(true, true),
  2366. false,
  2367. { split = 'left', win = old_curwin }
  2368. )
  2369. api.nvim_win_set_config(
  2370. old_curwin,
  2371. { relative = 'editor', row = 0, col = 0, width = 5, height = 5 }
  2372. )
  2373. eq('editor', api.nvim_win_get_config(old_curwin).relative)
  2374. -- ...which means we shouldn't be able to also make the new window floating too!
  2375. eq(
  2376. 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
  2377. pcall_err(
  2378. api.nvim_win_set_config,
  2379. t1_new_win,
  2380. { relative = 'editor', row = 0, col = 0, width = 5, height = 5 }
  2381. )
  2382. )
  2383. -- Nothing ought to stop us from making the cmdwin itself floating, though...
  2384. api.nvim_win_set_config(0, { relative = 'editor', row = 0, col = 0, width = 5, height = 5 })
  2385. eq('editor', api.nvim_win_get_config(0).relative)
  2386. -- We can't make our new window from before floating too, as it's now the only non-float.
  2387. eq(
  2388. 'Cannot change last window into float',
  2389. pcall_err(
  2390. api.nvim_win_set_config,
  2391. t1_new_win,
  2392. { relative = 'editor', row = 0, col = 0, width = 5, height = 5 }
  2393. )
  2394. )
  2395. command('quit!')
  2396. -- Can't switch away from window before moving it to a different tabpage during textlock.
  2397. exec(([[
  2398. new
  2399. call setline(1, 'foo')
  2400. setlocal debug=throw indentexpr=nvim_win_set_config(0,#{split:'left',win:%d})
  2401. ]]):format(t2_win))
  2402. local cur_win = api.nvim_get_current_win()
  2403. matches(
  2404. 'E565: Not allowed to change text or change window$',
  2405. pcall_err(command, 'normal! ==')
  2406. )
  2407. eq(cur_win, api.nvim_get_current_win())
  2408. end)
  2409. it('updates statusline when moving bottom split', function()
  2410. local screen = Screen.new(10, 10)
  2411. exec([[
  2412. set laststatus=0
  2413. belowright split
  2414. call nvim_win_set_config(0, #{split: 'above', win: win_getid(winnr('#'))})
  2415. ]])
  2416. screen:expect([[
  2417. ^ |
  2418. {1:~ }|*3
  2419. {3:[No Name] }|
  2420. |
  2421. {1:~ }|*3
  2422. |
  2423. ]])
  2424. end)
  2425. it("updates tp_curwin of moved window's original tabpage", function()
  2426. local t1 = api.nvim_get_current_tabpage()
  2427. command('tab split | split')
  2428. local t2 = api.nvim_get_current_tabpage()
  2429. local t2_alt_win = api.nvim_get_current_win()
  2430. command('vsplit')
  2431. local t2_cur_win = api.nvim_get_current_win()
  2432. command('tabprevious')
  2433. eq(t2_cur_win, api.nvim_tabpage_get_win(t2))
  2434. -- tp_curwin is unchanged when moved within the same tabpage.
  2435. api.nvim_win_set_config(t2_cur_win, { split = 'left', win = t2_alt_win })
  2436. eq(t2_cur_win, api.nvim_tabpage_get_win(t2))
  2437. -- Also unchanged if the move failed.
  2438. command('let &winwidth = &columns | let &winminwidth = &columns')
  2439. matches(
  2440. 'E36: Not enough room$',
  2441. pcall_err(api.nvim_win_set_config, t2_cur_win, { split = 'left', win = 0 })
  2442. )
  2443. eq(t2_cur_win, api.nvim_tabpage_get_win(t2))
  2444. command('set winminwidth& winwidth&')
  2445. -- But is changed if successfully moved to a different tabpage.
  2446. api.nvim_win_set_config(t2_cur_win, { split = 'left', win = 0 })
  2447. eq(t2_alt_win, api.nvim_tabpage_get_win(t2))
  2448. eq(t1, api.nvim_win_get_tabpage(t2_cur_win))
  2449. -- Now do it for a float, which has different altwin logic.
  2450. command('tabnext')
  2451. t2_cur_win =
  2452. api.nvim_open_win(0, true, { relative = 'editor', row = 5, col = 5, width = 5, height = 5 })
  2453. eq(t2_alt_win, fn.win_getid(fn.winnr('#')))
  2454. command('tabprevious')
  2455. eq(t2_cur_win, api.nvim_tabpage_get_win(t2))
  2456. api.nvim_win_set_config(t2_cur_win, { split = 'left', win = 0 })
  2457. eq(t2_alt_win, api.nvim_tabpage_get_win(t2))
  2458. eq(t1, api.nvim_win_get_tabpage(t2_cur_win))
  2459. end)
  2460. end)
  2461. describe('get_config', function()
  2462. it('includes border', function()
  2463. local b = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' }
  2464. local win = api.nvim_open_win(0, true, {
  2465. relative = 'win',
  2466. row = 3,
  2467. col = 3,
  2468. width = 12,
  2469. height = 3,
  2470. border = b,
  2471. })
  2472. local cfg = api.nvim_win_get_config(win)
  2473. eq(b, cfg.border)
  2474. end)
  2475. it('includes border with highlight group', function()
  2476. local b = {
  2477. { 'a', 'Normal' },
  2478. { 'b', 'Special' },
  2479. { 'c', 'String' },
  2480. { 'd', 'Comment' },
  2481. { 'e', 'Visual' },
  2482. { 'f', 'Error' },
  2483. { 'g', 'Constant' },
  2484. { 'h', 'PreProc' },
  2485. }
  2486. local win = api.nvim_open_win(0, true, {
  2487. relative = 'win',
  2488. row = 3,
  2489. col = 3,
  2490. width = 12,
  2491. height = 3,
  2492. border = b,
  2493. })
  2494. local cfg = api.nvim_win_get_config(win)
  2495. eq(b, cfg.border)
  2496. end)
  2497. it('includes title and footer', function()
  2498. local title = { { 'A', { 'StatusLine', 'TabLine' } }, { 'B' }, { 'C', 'WinBar' } }
  2499. local footer = { { 'A', 'WinBar' }, { 'B' }, { 'C', { 'StatusLine', 'TabLine' } } }
  2500. local win = api.nvim_open_win(0, true, {
  2501. relative = 'win',
  2502. row = 3,
  2503. col = 3,
  2504. width = 12,
  2505. height = 3,
  2506. border = 'single',
  2507. title = title,
  2508. footer = footer,
  2509. })
  2510. local cfg = api.nvim_win_get_config(win)
  2511. eq(title, cfg.title)
  2512. eq(footer, cfg.footer)
  2513. end)
  2514. it('includes split for normal windows', function()
  2515. local win = api.nvim_open_win(0, true, {
  2516. vertical = true,
  2517. win = -1,
  2518. })
  2519. eq('left', api.nvim_win_get_config(win).split)
  2520. api.nvim_win_set_config(win, {
  2521. vertical = false,
  2522. win = -1,
  2523. })
  2524. eq('above', api.nvim_win_get_config(win).split)
  2525. api.nvim_win_set_config(win, {
  2526. split = 'below',
  2527. win = -1,
  2528. })
  2529. eq('below', api.nvim_win_get_config(win).split)
  2530. end)
  2531. it('includes split when splitting with ex commands', function()
  2532. local win = api.nvim_get_current_win()
  2533. eq('left', api.nvim_win_get_config(win).split)
  2534. command('vsplit')
  2535. local win2 = api.nvim_get_current_win()
  2536. -- initial window now be marked as right split
  2537. -- since it was split with a vertical split
  2538. -- and 'splitright' is false by default
  2539. eq('right', api.nvim_win_get_config(win).split)
  2540. eq('left', api.nvim_win_get_config(win2).split)
  2541. api.nvim_set_option_value('splitbelow', true, {
  2542. scope = 'global',
  2543. })
  2544. api.nvim_win_close(win, true)
  2545. command('split')
  2546. local win3 = api.nvim_get_current_win()
  2547. eq('below', api.nvim_win_get_config(win3).split)
  2548. end)
  2549. it("includes the correct 'split' option in complex layouts", function()
  2550. local initial_win = api.nvim_get_current_win()
  2551. local win = api.nvim_open_win(0, false, {
  2552. split = 'right',
  2553. win = -1,
  2554. })
  2555. local win2 = api.nvim_open_win(0, false, {
  2556. split = 'below',
  2557. win = win,
  2558. })
  2559. api.nvim_win_set_config(win2, {
  2560. width = 50,
  2561. })
  2562. api.nvim_win_set_config(win, {
  2563. split = 'left',
  2564. win = -1,
  2565. })
  2566. local win3 = api.nvim_open_win(0, false, {
  2567. split = 'above',
  2568. win = -1,
  2569. })
  2570. local float = api.nvim_open_win(0, false, {
  2571. relative = 'editor',
  2572. width = 40,
  2573. height = 20,
  2574. col = 20,
  2575. row = 10,
  2576. })
  2577. api.nvim_win_set_config(float, {
  2578. split = 'right',
  2579. win = -1,
  2580. })
  2581. local layout = fn.winlayout()
  2582. eq({
  2583. 'row',
  2584. {
  2585. {
  2586. 'col',
  2587. {
  2588. { 'leaf', win3 },
  2589. {
  2590. 'row',
  2591. {
  2592. { 'leaf', win },
  2593. { 'leaf', initial_win },
  2594. { 'leaf', win2 },
  2595. },
  2596. },
  2597. },
  2598. },
  2599. {
  2600. 'leaf',
  2601. float,
  2602. },
  2603. },
  2604. }, layout)
  2605. eq('above', api.nvim_win_get_config(win3).split)
  2606. eq('left', api.nvim_win_get_config(win).split)
  2607. eq('left', api.nvim_win_get_config(initial_win).split)
  2608. eq('right', api.nvim_win_get_config(win2).split)
  2609. eq('right', api.nvim_win_get_config(float).split)
  2610. end)
  2611. end)
  2612. describe('set_config', function()
  2613. it('no crash with invalid title', function()
  2614. local win = api.nvim_open_win(0, true, {
  2615. width = 10,
  2616. height = 10,
  2617. relative = 'editor',
  2618. row = 10,
  2619. col = 10,
  2620. title = { { 'test' } },
  2621. border = 'single',
  2622. })
  2623. eq(
  2624. 'title/footer must be string or array',
  2625. pcall_err(api.nvim_win_set_config, win, { title = 0 })
  2626. )
  2627. command('redraw!')
  2628. assert_alive()
  2629. eq(
  2630. 'title/footer cannot be an empty array',
  2631. pcall_err(api.nvim_win_set_config, win, { title = {} })
  2632. )
  2633. command('redraw!')
  2634. assert_alive()
  2635. end)
  2636. it('no crash with invalid footer', function()
  2637. local win = api.nvim_open_win(0, true, {
  2638. width = 10,
  2639. height = 10,
  2640. relative = 'editor',
  2641. row = 10,
  2642. col = 10,
  2643. footer = { { 'test' } },
  2644. border = 'single',
  2645. })
  2646. eq(
  2647. 'title/footer must be string or array',
  2648. pcall_err(api.nvim_win_set_config, win, { footer = 0 })
  2649. )
  2650. command('redraw!')
  2651. assert_alive()
  2652. eq(
  2653. 'title/footer cannot be an empty array',
  2654. pcall_err(api.nvim_win_set_config, win, { footer = {} })
  2655. )
  2656. command('redraw!')
  2657. assert_alive()
  2658. end)
  2659. describe('no crash or memory leak', function()
  2660. local win
  2661. before_each(function()
  2662. win = api.nvim_open_win(0, false, {
  2663. relative = 'editor',
  2664. row = 10,
  2665. col = 10,
  2666. height = 10,
  2667. width = 10,
  2668. border = 'single',
  2669. title = { { 'OLD_TITLE' } },
  2670. footer = { { 'OLD_FOOTER' } },
  2671. })
  2672. end)
  2673. it('with valid title and invalid footer', function()
  2674. eq(
  2675. 'title/footer must be string or array',
  2676. pcall_err(api.nvim_win_set_config, win, {
  2677. title = { { 'NEW_TITLE' } },
  2678. footer = 0,
  2679. })
  2680. )
  2681. command('redraw!')
  2682. assert_alive()
  2683. eq({ { 'OLD_TITLE' } }, api.nvim_win_get_config(win).title)
  2684. end)
  2685. it('with invalid title and valid footer', function()
  2686. eq(
  2687. 'title/footer must be string or array',
  2688. pcall_err(api.nvim_win_set_config, win, {
  2689. title = 0,
  2690. footer = { { 'NEW_FOOTER' } },
  2691. })
  2692. )
  2693. command('redraw!')
  2694. assert_alive()
  2695. eq({ { 'OLD_FOOTER' } }, api.nvim_win_get_config(win).footer)
  2696. end)
  2697. end)
  2698. end)
  2699. end)