window_spec.lua 106 KB

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