with_spec.lua 50 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625
  1. local t = require('test.testutil')
  2. local n = require('test.functional.testnvim')()
  3. local Screen = require('test.functional.ui.screen')
  4. local fn = n.fn
  5. local api = n.api
  6. local command = n.command
  7. local eq = t.eq
  8. local exec_lua = n.exec_lua
  9. local exec_capture = n.exec_capture
  10. local matches = t.matches
  11. local pcall_err = t.pcall_err
  12. describe('vim._with', function()
  13. before_each(function()
  14. n.clear()
  15. exec_lua([[
  16. _G.fn = vim.fn
  17. _G.api = vim.api
  18. _G.setup_buffers = function()
  19. return api.nvim_create_buf(false, true), api.nvim_get_current_buf()
  20. end
  21. _G.setup_windows = function()
  22. local other_win = api.nvim_get_current_win()
  23. vim.cmd.new()
  24. return other_win, api.nvim_get_current_win()
  25. end
  26. ]])
  27. end)
  28. local assert_events_trigger = function()
  29. local out = exec_lua [[
  30. -- Needs three global values defined:
  31. -- - `test_events` - array of events which are tested.
  32. -- - `test_context` - context to be tested.
  33. -- - `test_trig_event` - callable triggering at least one tested event.
  34. _G.n_events = 0
  35. local opts = { callback = function() _G.n_events = _G.n_events + 1 end }
  36. api.nvim_create_autocmd(_G.test_events, opts)
  37. local context = { bo = { commentstring = '-- %s' } }
  38. -- Should not trigger events on its own
  39. vim._with(_G.test_context, function() end)
  40. local is_no_events = _G.n_events == 0
  41. -- Should trigger events if specifically asked inside callback
  42. local is_events = vim._with(_G.test_context, function()
  43. _G.test_trig_event()
  44. return _G.n_events > 0
  45. end)
  46. return { is_no_events, is_events }
  47. ]]
  48. eq({ true, true }, out)
  49. end
  50. describe('`bo` context', function()
  51. before_each(function()
  52. exec_lua [[
  53. _G.other_buf, _G.cur_buf = setup_buffers()
  54. -- 'commentstring' is local to buffer and string
  55. vim.bo[other_buf].commentstring = '## %s'
  56. vim.bo[cur_buf].commentstring = '// %s'
  57. vim.go.commentstring = '$$ %s'
  58. -- 'undolevels' is global or local to buffer (global-local) and number
  59. vim.bo[other_buf].undolevels = 100
  60. vim.bo[cur_buf].undolevels = 250
  61. vim.go.undolevels = 500
  62. _G.get_state = function()
  63. return {
  64. bo = {
  65. cms_cur = vim.bo[cur_buf].commentstring,
  66. cms_other = vim.bo[other_buf].commentstring,
  67. ul_cur = vim.bo[cur_buf].undolevels,
  68. ul_other = vim.bo[other_buf].undolevels,
  69. },
  70. go = {
  71. cms = vim.go.commentstring,
  72. ul = vim.go.undolevels,
  73. },
  74. }
  75. end
  76. ]]
  77. end)
  78. it('works', function()
  79. local out = exec_lua [[
  80. local context = { bo = { commentstring = '-- %s', undolevels = 0 } }
  81. local before = get_state()
  82. local inner = vim._with(context, function()
  83. assert(api.nvim_get_current_buf() == cur_buf)
  84. return get_state()
  85. end)
  86. return { before = before, inner = inner, after = get_state() }
  87. ]]
  88. eq({
  89. bo = { cms_cur = '-- %s', cms_other = '## %s', ul_cur = 0, ul_other = 100 },
  90. go = { cms = '$$ %s', ul = 500 },
  91. }, out.inner)
  92. eq(out.before, out.after)
  93. end)
  94. it('sets options in `buf` context', function()
  95. local out = exec_lua [[
  96. local context = { buf = other_buf, bo = { commentstring = '-- %s', undolevels = 0 } }
  97. local before = get_state()
  98. local inner = vim._with(context, function()
  99. assert(api.nvim_get_current_buf() == other_buf)
  100. return get_state()
  101. end)
  102. return { before = before, inner = inner, after = get_state() }
  103. ]]
  104. eq({
  105. bo = { cms_cur = '// %s', cms_other = '-- %s', ul_cur = 250, ul_other = 0 },
  106. go = { cms = '$$ %s', ul = 500 },
  107. }, out.inner)
  108. eq(out.before, out.after)
  109. end)
  110. it('restores only options from context', function()
  111. local out = exec_lua [[
  112. local context = { bo = { commentstring = '-- %s' } }
  113. local inner = vim._with(context, function()
  114. assert(api.nvim_get_current_buf() == cur_buf)
  115. vim.bo[cur_buf].undolevels = 750
  116. vim.bo[cur_buf].commentstring = '!! %s'
  117. return get_state()
  118. end)
  119. return { inner = inner, after = get_state() }
  120. ]]
  121. eq({
  122. bo = { cms_cur = '!! %s', cms_other = '## %s', ul_cur = 750, ul_other = 100 },
  123. go = { cms = '$$ %s', ul = 500 },
  124. }, out.inner)
  125. eq({
  126. bo = { cms_cur = '// %s', cms_other = '## %s', ul_cur = 750, ul_other = 100 },
  127. go = { cms = '$$ %s', ul = 500 },
  128. }, out.after)
  129. end)
  130. it('does not trigger events', function()
  131. exec_lua [[
  132. _G.test_events = { 'BufEnter', 'BufLeave', 'BufWinEnter', 'BufWinLeave' }
  133. _G.test_context = { bo = { commentstring = '-- %s' } }
  134. _G.test_trig_event = function() vim.cmd.new() end
  135. ]]
  136. assert_events_trigger()
  137. end)
  138. it('can be nested', function()
  139. local out = exec_lua [[
  140. local before, before_inner, after_inner = get_state(), nil, nil
  141. vim._with({ bo = { commentstring = '-- %s', undolevels = 0 } }, function()
  142. before_inner = get_state()
  143. inner = vim._with({ bo = { commentstring = '!! %s' } }, get_state)
  144. after_inner = get_state()
  145. end)
  146. return {
  147. before = before, before_inner = before_inner,
  148. inner = inner,
  149. after_inner = after_inner, after = get_state(),
  150. }
  151. ]]
  152. eq('!! %s', out.inner.bo.cms_cur)
  153. eq(0, out.inner.bo.ul_cur)
  154. eq(out.before_inner, out.after_inner)
  155. eq(out.before, out.after)
  156. end)
  157. end)
  158. describe('`buf` context', function()
  159. it('works', function()
  160. local out = exec_lua [[
  161. local other_buf, cur_buf = setup_buffers()
  162. local inner = vim._with({ buf = other_buf }, function()
  163. return api.nvim_get_current_buf()
  164. end)
  165. return { inner == other_buf, api.nvim_get_current_buf() == cur_buf }
  166. ]]
  167. eq({ true, true }, out)
  168. end)
  169. it('does not trigger events', function()
  170. exec_lua [[
  171. _G.test_events = { 'BufEnter', 'BufLeave', 'BufWinEnter', 'BufWinLeave' }
  172. _G.test_context = { buf = other_buf }
  173. _G.test_trig_event = function() vim.cmd.new() end
  174. ]]
  175. assert_events_trigger()
  176. end)
  177. it('can access buffer options', function()
  178. local out = exec_lua [[
  179. other_buf, cur_buf = setup_buffers()
  180. vim.bo[other_buf].commentstring = '## %s'
  181. vim.bo[cur_buf].commentstring = '// %s'
  182. vim._with({ buf = other_buf }, function()
  183. vim.cmd.set('commentstring=--\\ %s')
  184. end)
  185. return vim.bo[other_buf].commentstring == '-- %s' and
  186. vim.bo[cur_buf].commentstring == '// %s'
  187. ]]
  188. eq(true, out)
  189. end)
  190. it('works with different kinds of buffers', function()
  191. exec_lua [[
  192. local assert_buf = function(buf)
  193. vim._with({ buf = buf }, function()
  194. assert(api.nvim_get_current_buf() == buf)
  195. end)
  196. end
  197. -- Current
  198. assert_buf(api.nvim_get_current_buf())
  199. -- Hidden listed
  200. local listed = api.nvim_create_buf(true, true)
  201. assert_buf(listed)
  202. -- Visible
  203. local other_win, cur_win = setup_windows()
  204. api.nvim_win_set_buf(other_win, listed)
  205. assert_buf(listed)
  206. -- Shown but not visible
  207. vim.cmd.tabnew()
  208. assert_buf(listed)
  209. -- Shown in several windows
  210. api.nvim_win_set_buf(0, listed)
  211. assert_buf(listed)
  212. -- Shown in floating window
  213. local float_buf = api.nvim_create_buf(false, true)
  214. local config = { relative = 'editor', row = 1, col = 1, width = 5, height = 5 }
  215. api.nvim_open_win(float_buf, false, config)
  216. assert_buf(float_buf)
  217. ]]
  218. end)
  219. it('does not cause ml_get errors with invalid visual selection', function()
  220. exec_lua [[
  221. api.nvim_buf_set_lines(0, 0, -1, true, { 'a', 'b', 'c' })
  222. api.nvim_feedkeys(vim.keycode('G<C-V>'), 'txn', false)
  223. local other_buf, _ = setup_buffers()
  224. vim._with({ buf = buf }, function() vim.cmd.redraw() end)
  225. ]]
  226. end)
  227. it('can be nested', function()
  228. exec_lua [[
  229. local other_buf, cur_buf = setup_buffers()
  230. vim._with({ buf = other_buf }, function()
  231. assert(api.nvim_get_current_buf() == other_buf)
  232. inner = vim._with({ buf = cur_buf }, function()
  233. assert(api.nvim_get_current_buf() == cur_buf)
  234. end)
  235. assert(api.nvim_get_current_buf() == other_buf)
  236. end)
  237. assert(api.nvim_get_current_buf() == cur_buf)
  238. ]]
  239. end)
  240. it('can be nested crazily with hidden buffers', function()
  241. local out = exec_lua([[
  242. local n = 0
  243. local function with_recursive_nested_bufs()
  244. n = n + 1
  245. if n > 20 then return true end
  246. local other_buf, _ = setup_buffers()
  247. vim.bo[other_buf].commentstring = '## %s'
  248. local callback = function()
  249. return api.nvim_get_current_buf() == other_buf
  250. and vim.bo[other_buf].commentstring == '## %s'
  251. and with_recursive_nested_bufs()
  252. end
  253. return vim._with({ buf = other_buf }, callback) and
  254. api.nvim_buf_delete(other_buf, {}) == nil
  255. end
  256. return with_recursive_nested_bufs()
  257. ]])
  258. eq(true, out)
  259. end)
  260. end)
  261. describe('`emsg_silent` context', function()
  262. pending('works', function()
  263. local ok = pcall(
  264. exec_lua,
  265. [[
  266. _G.f = function()
  267. error('This error should not interfer with execution', 0)
  268. end
  269. -- Should not produce error same as `vim.cmd('silent! lua _G.f()')`
  270. vim._with({ emsg_silent = true }, f)
  271. ]]
  272. )
  273. eq(true, ok)
  274. -- Should properly report errors afterwards
  275. ok = pcall(exec_lua, 'lua _G.f()')
  276. eq(false, ok)
  277. end)
  278. it('can be nested', function()
  279. local ok = pcall(
  280. exec_lua,
  281. [[
  282. _G.f = function()
  283. error('This error should not interfer with execution', 0)
  284. end
  285. -- Should produce error same as `_G.f()`
  286. vim._with({ emsg_silent = true }, function()
  287. vim._with( { emsg_silent = false }, f)
  288. end)
  289. ]]
  290. )
  291. eq(false, ok)
  292. end)
  293. end)
  294. describe('`env` context', function()
  295. before_each(function()
  296. exec_lua [[
  297. vim.fn.setenv('aaa', 'hello')
  298. _G.get_state = function()
  299. return { aaa = vim.fn.getenv('aaa'), bbb = vim.fn.getenv('bbb') }
  300. end
  301. ]]
  302. end)
  303. it('works', function()
  304. local out = exec_lua [[
  305. local context = { env = { aaa = 'inside', bbb = 'wow' } }
  306. local before = get_state()
  307. local inner = vim._with(context, get_state)
  308. return { before = before, inner = inner, after = get_state() }
  309. ]]
  310. eq({ aaa = 'inside', bbb = 'wow' }, out.inner)
  311. eq(out.before, out.after)
  312. end)
  313. it('restores only variables from context', function()
  314. local out = exec_lua [[
  315. local context = { env = { bbb = 'wow' } }
  316. local before = get_state()
  317. local inner = vim._with(context, function()
  318. vim.env.aaa = 'inside'
  319. return get_state()
  320. end)
  321. return { before = before, inner = inner, after = get_state() }
  322. ]]
  323. eq({ aaa = 'inside', bbb = 'wow' }, out.inner)
  324. eq({ aaa = 'inside', bbb = vim.NIL }, out.after)
  325. end)
  326. it('can be nested', function()
  327. local out = exec_lua [[
  328. local before, before_inner, after_inner = get_state(), nil, nil
  329. vim._with({ env = { aaa = 'inside', bbb = 'wow' } }, function()
  330. before_inner = get_state()
  331. inner = vim._with({ env = { aaa = 'more inside' } }, get_state)
  332. after_inner = get_state()
  333. end)
  334. return {
  335. before = before, before_inner = before_inner,
  336. inner = inner,
  337. after_inner = after_inner, after = get_state(),
  338. }
  339. ]]
  340. eq('more inside', out.inner.aaa)
  341. eq('wow', out.inner.bbb)
  342. eq(out.before_inner, out.after_inner)
  343. eq(out.before, out.after)
  344. end)
  345. end)
  346. describe('`go` context', function()
  347. before_each(function()
  348. exec_lua [[
  349. vim.bo.commentstring = '## %s'
  350. vim.go.commentstring = '$$ %s'
  351. vim.wo.winblend = 25
  352. vim.go.winblend = 50
  353. vim.go.langmap = 'xy,yx'
  354. _G.get_state = function()
  355. return {
  356. bo = { cms = vim.bo.commentstring },
  357. wo = { winbl = vim.wo.winblend },
  358. go = {
  359. cms = vim.go.commentstring,
  360. winbl = vim.go.winblend,
  361. lmap = vim.go.langmap,
  362. },
  363. }
  364. end
  365. ]]
  366. end)
  367. it('works', function()
  368. local out = exec_lua [[
  369. local context = {
  370. go = { commentstring = '-- %s', winblend = 75, langmap = 'ab,ba' },
  371. }
  372. local before = get_state()
  373. local inner = vim._with(context, get_state)
  374. return { before = before, inner = inner, after = get_state() }
  375. ]]
  376. eq({
  377. bo = { cms = '## %s' },
  378. wo = { winbl = 25 },
  379. go = { cms = '-- %s', winbl = 75, lmap = 'ab,ba' },
  380. }, out.inner)
  381. eq(out.before, out.after)
  382. end)
  383. it('works with `eventignore`', function()
  384. -- This might be an issue if saving and restoring option context is done
  385. -- to account for triggering `OptionSet`, but in not a good way
  386. local out = exec_lua [[
  387. vim.go.eventignore = 'ModeChanged'
  388. local inner = vim._with({ go = { eventignore = 'CursorMoved' } }, function()
  389. return vim.go.eventignore
  390. end)
  391. return { inner = inner, after = vim.go.eventignore }
  392. ]]
  393. eq({ inner = 'CursorMoved', after = 'ModeChanged' }, out)
  394. end)
  395. it('restores only options from context', function()
  396. local out = exec_lua [[
  397. local context = { go = { langmap = 'ab,ba' } }
  398. local inner = vim._with(context, function()
  399. vim.go.commentstring = '!! %s'
  400. vim.go.winblend = 75
  401. vim.go.langmap = 'uv,vu'
  402. return get_state()
  403. end)
  404. return { inner = inner, after = get_state() }
  405. ]]
  406. eq({
  407. bo = { cms = '## %s' },
  408. wo = { winbl = 25 },
  409. go = { cms = '!! %s', winbl = 75, lmap = 'uv,vu' },
  410. }, out.inner)
  411. eq({
  412. bo = { cms = '## %s' },
  413. wo = { winbl = 25 },
  414. go = { cms = '!! %s', winbl = 75, lmap = 'xy,yx' },
  415. }, out.after)
  416. end)
  417. it('does not trigger events', function()
  418. exec_lua [[
  419. _G.test_events = {
  420. 'BufEnter', 'BufLeave', 'BufWinEnter', 'BufWinLeave', 'WinEnter', 'WinLeave'
  421. }
  422. _G.test_context = { go = { commentstring = '-- %s', winblend = 75, langmap = 'ab,ba' } }
  423. _G.test_trig_event = function() vim.cmd.new() end
  424. ]]
  425. assert_events_trigger()
  426. end)
  427. it('can be nested', function()
  428. local out = exec_lua [[
  429. local before, before_inner, after_inner = get_state(), nil, nil
  430. vim._with({ go = { langmap = 'ab,ba', commentstring = '-- %s' } }, function()
  431. before_inner = get_state()
  432. inner = vim._with({ go = { langmap = 'uv,vu' } }, get_state)
  433. after_inner = get_state()
  434. end)
  435. return {
  436. before = before, before_inner = before_inner,
  437. inner = inner,
  438. after_inner = after_inner, after = get_state(),
  439. }
  440. ]]
  441. eq('uv,vu', out.inner.go.lmap)
  442. eq('-- %s', out.inner.go.cms)
  443. eq(out.before_inner, out.after_inner)
  444. eq(out.before, out.after)
  445. end)
  446. end)
  447. describe('`hide` context', function()
  448. pending('works', function()
  449. local ok = pcall(
  450. exec_lua,
  451. [[
  452. vim.o.hidden = false
  453. vim.bo.modified = true
  454. local init_buf = api.nvim_get_current_buf()
  455. -- Should not produce error same as `vim.cmd('hide enew')`
  456. vim._with({ hide = true }, function()
  457. vim.cmd.enew()
  458. end)
  459. assert(api.nvim_get_current_buf() ~= init_buf)
  460. ]]
  461. )
  462. eq(true, ok)
  463. end)
  464. it('can be nested', function()
  465. local ok = pcall(
  466. exec_lua,
  467. [[
  468. vim.o.hidden = false
  469. vim.bo.modified = true
  470. -- Should produce error same as `vim.cmd.enew()`
  471. vim._with({ hide = true }, function()
  472. vim._with({ hide = false }, function()
  473. vim.cmd.enew()
  474. end)
  475. end)
  476. ]]
  477. )
  478. eq(false, ok)
  479. end)
  480. end)
  481. describe('`horizontal` context', function()
  482. local is_approx_eq = function(dim, id_1, id_2)
  483. local f = dim == 'height' and api.nvim_win_get_height or api.nvim_win_get_width
  484. return math.abs(f(id_1) - f(id_2)) <= 1
  485. end
  486. local win_id_1, win_id_2, win_id_3
  487. before_each(function()
  488. win_id_1 = api.nvim_get_current_win()
  489. command('wincmd v | wincmd 5>')
  490. win_id_2 = api.nvim_get_current_win()
  491. command('wincmd s | wincmd 5+')
  492. win_id_3 = api.nvim_get_current_win()
  493. eq(is_approx_eq('width', win_id_1, win_id_2), false)
  494. eq(is_approx_eq('height', win_id_3, win_id_2), false)
  495. end)
  496. pending('works', function()
  497. exec_lua [[
  498. -- Should be same as `vim.cmd('horizontal wincmd =')`
  499. vim._with({ horizontal = true }, function()
  500. vim.cmd.wincmd('=')
  501. end)
  502. ]]
  503. eq(is_approx_eq('width', win_id_1, win_id_2), true)
  504. eq(is_approx_eq('height', win_id_3, win_id_2), false)
  505. end)
  506. pending('can be nested', function()
  507. exec_lua [[
  508. -- Should be same as `vim.cmd.wincmd('=')`
  509. vim._with({ horizontal = true }, function()
  510. vim._with({ horizontal = false }, function()
  511. vim.cmd.wincmd('=')
  512. end)
  513. end)
  514. ]]
  515. eq(is_approx_eq('width', win_id_1, win_id_2), true)
  516. eq(is_approx_eq('height', win_id_3, win_id_2), true)
  517. end)
  518. end)
  519. describe('`keepalt` context', function()
  520. pending('works', function()
  521. local out = exec_lua [[
  522. vim.cmd('edit alt')
  523. vim.cmd('edit new')
  524. assert(fn.bufname('#') == 'alt')
  525. -- Should work as `vim.cmd('keepalt edit very-new')`
  526. vim._with({ keepalt = true }, function()
  527. vim.cmd.edit('very-new')
  528. end)
  529. return fn.bufname('#') == 'alt'
  530. ]]
  531. eq(true, out)
  532. end)
  533. it('can be nested', function()
  534. local out = exec_lua [[
  535. vim.cmd('edit alt')
  536. vim.cmd('edit new')
  537. assert(fn.bufname('#') == 'alt')
  538. -- Should work as `vim.cmd.edit('very-new')`
  539. vim._with({ keepalt = true }, function()
  540. vim._with({ keepalt = false }, function()
  541. vim.cmd.edit('very-new')
  542. end)
  543. end)
  544. return fn.bufname('#') == 'alt'
  545. ]]
  546. eq(false, out)
  547. end)
  548. end)
  549. describe('`keepjumps` context', function()
  550. pending('works', function()
  551. local out = exec_lua [[
  552. api.nvim_buf_set_lines(0, 0, -1, false, { 'aaa', 'bbb', 'ccc' })
  553. local jumplist_before = fn.getjumplist()
  554. -- Should work as `vim.cmd('keepjumps normal! Ggg')`
  555. vim._with({ keepjumps = true }, function()
  556. vim.cmd('normal! Ggg')
  557. end)
  558. return vim.deep_equal(jumplist_before, fn.getjumplist())
  559. ]]
  560. eq(true, out)
  561. end)
  562. it('can be nested', function()
  563. local out = exec_lua [[
  564. api.nvim_buf_set_lines(0, 0, -1, false, { 'aaa', 'bbb', 'ccc' })
  565. local jumplist_before = fn.getjumplist()
  566. vim._with({ keepjumps = true }, function()
  567. vim._with({ keepjumps = false }, function()
  568. vim.cmd('normal! Ggg')
  569. end)
  570. end)
  571. return vim.deep_equal(jumplist_before, fn.getjumplist())
  572. ]]
  573. eq(false, out)
  574. end)
  575. end)
  576. describe('`keepmarks` context', function()
  577. pending('works', function()
  578. local out = exec_lua [[
  579. vim.cmd('set cpoptions+=R')
  580. api.nvim_buf_set_lines(0, 0, -1, false, { 'bbb', 'ccc', 'aaa' })
  581. api.nvim_buf_set_mark(0, 'm', 2, 2, {})
  582. -- Should be the same as `vim.cmd('keepmarks %!sort')`
  583. vim._with({ keepmarks = true }, function()
  584. vim.cmd('%!sort')
  585. end)
  586. return api.nvim_buf_get_mark(0, 'm')
  587. ]]
  588. eq({ 2, 2 }, out)
  589. end)
  590. it('can be nested', function()
  591. local out = exec_lua [[
  592. vim.cmd('set cpoptions+=R')
  593. api.nvim_buf_set_lines(0, 0, -1, false, { 'bbb', 'ccc', 'aaa' })
  594. api.nvim_buf_set_mark(0, 'm', 2, 2, {})
  595. vim._with({ keepmarks = true }, function()
  596. vim._with({ keepmarks = false }, function()
  597. vim.cmd('%!sort')
  598. end)
  599. end)
  600. return api.nvim_buf_get_mark(0, 'm')
  601. ]]
  602. eq({ 0, 2 }, out)
  603. end)
  604. end)
  605. describe('`keepatterns` context', function()
  606. pending('works', function()
  607. local out = exec_lua [[
  608. api.nvim_buf_set_lines(0, 0, -1, false, { 'aaa', 'bbb' })
  609. vim.cmd('/aaa')
  610. -- Should be the same as `vim.cmd('keeppatterns /bbb')`
  611. vim._with({ keeppatterns = true }, function()
  612. vim.cmd('/bbb')
  613. end)
  614. return fn.getreg('/')
  615. ]]
  616. eq('aaa', out)
  617. end)
  618. it('can be nested', function()
  619. local out = exec_lua [[
  620. api.nvim_buf_set_lines(0, 0, -1, false, { 'aaa', 'bbb' })
  621. vim.cmd('/aaa')
  622. vim._with({ keeppatterns = true }, function()
  623. vim._with({ keeppatterns = false }, function()
  624. vim.cmd('/bbb')
  625. end)
  626. end)
  627. return fn.getreg('/')
  628. ]]
  629. eq('bbb', out)
  630. end)
  631. end)
  632. describe('`lockmarks` context', function()
  633. it('works', function()
  634. local mark = exec_lua [[
  635. api.nvim_buf_set_lines(0, 0, 0, false, { 'aaa', 'bbb', 'ccc' })
  636. api.nvim_buf_set_mark(0, 'm', 2, 2, {})
  637. -- Should be same as `:lockmarks lua api.nvim_buf_set_lines(...)`
  638. vim._with({ lockmarks = true }, function()
  639. api.nvim_buf_set_lines(0, 0, 2, false, { 'uuu', 'vvv', 'www' })
  640. end)
  641. return api.nvim_buf_get_mark(0, 'm')
  642. ]]
  643. eq({ 2, 2 }, mark)
  644. end)
  645. it('can be nested', function()
  646. local mark = exec_lua [[
  647. api.nvim_buf_set_lines(0, 0, 0, false, { 'aaa', 'bbb', 'ccc' })
  648. api.nvim_buf_set_mark(0, 'm', 2, 2, {})
  649. vim._with({ lockmarks = true }, function()
  650. vim._with({ lockmarks = false }, function()
  651. api.nvim_buf_set_lines(0, 0, 2, false, { 'uuu', 'vvv', 'www' })
  652. end)
  653. end)
  654. return api.nvim_buf_get_mark(0, 'm')
  655. ]]
  656. eq({ 0, 2 }, mark)
  657. end)
  658. end)
  659. describe('`noautocmd` context', function()
  660. it('works', function()
  661. local out = exec_lua [[
  662. _G.n_events = 0
  663. vim.cmd('au ModeChanged * lua _G.n_events = _G.n_events + 1')
  664. -- Should be the same as `vim.cmd('noautocmd normal! vv')`
  665. vim._with({ noautocmd = true }, function()
  666. vim.cmd('normal! vv')
  667. end)
  668. return _G.n_events
  669. ]]
  670. eq(0, out)
  671. end)
  672. it('works with User events', function()
  673. local out = exec_lua [[
  674. _G.n_events = 0
  675. vim.cmd('au User MyEvent lua _G.n_events = _G.n_events + 1')
  676. -- Should be the same as `vim.cmd('noautocmd doautocmd User MyEvent')`
  677. vim._with({ noautocmd = true }, function()
  678. api.nvim_exec_autocmds('User', { pattern = 'MyEvent' })
  679. end)
  680. return _G.n_events
  681. ]]
  682. eq(0, out)
  683. end)
  684. pending('can be nested', function()
  685. local out = exec_lua [[
  686. _G.n_events = 0
  687. vim.cmd('au ModeChanged * lua _G.n_events = _G.n_events + 1')
  688. vim._with({ noautocmd = true }, function()
  689. vim._with({ noautocmd = false }, function()
  690. vim.cmd('normal! vv')
  691. end)
  692. end)
  693. return _G.n_events
  694. ]]
  695. eq(2, out)
  696. end)
  697. end)
  698. describe('`o` context', function()
  699. before_each(function()
  700. exec_lua [[
  701. _G.other_win, _G.cur_win = setup_windows()
  702. _G.other_buf, _G.cur_buf = setup_buffers()
  703. vim.bo[other_buf].commentstring = '## %s'
  704. vim.bo[cur_buf].commentstring = '// %s'
  705. vim.go.commentstring = '$$ %s'
  706. vim.bo[other_buf].undolevels = 100
  707. vim.bo[cur_buf].undolevels = 250
  708. vim.go.undolevels = 500
  709. vim.wo[other_win].virtualedit = 'block'
  710. vim.wo[cur_win].virtualedit = 'insert'
  711. vim.go.virtualedit = 'none'
  712. vim.wo[other_win].winblend = 10
  713. vim.wo[cur_win].winblend = 25
  714. vim.go.winblend = 50
  715. vim.go.langmap = 'xy,yx'
  716. _G.get_state = function()
  717. return {
  718. bo = {
  719. cms_cur = vim.bo[cur_buf].commentstring,
  720. cms_other = vim.bo[other_buf].commentstring,
  721. ul_cur = vim.bo[cur_buf].undolevels,
  722. ul_other = vim.bo[other_buf].undolevels,
  723. },
  724. wo = {
  725. ve_cur = vim.wo[cur_win].virtualedit,
  726. ve_other = vim.wo[other_win].virtualedit,
  727. winbl_cur = vim.wo[cur_win].winblend,
  728. winbl_other = vim.wo[other_win].winblend,
  729. },
  730. go = {
  731. cms = vim.go.commentstring,
  732. ul = vim.go.undolevels,
  733. ve = vim.go.virtualedit,
  734. winbl = vim.go.winblend,
  735. lmap = vim.go.langmap,
  736. },
  737. }
  738. end
  739. ]]
  740. end)
  741. it('works', function()
  742. local out = exec_lua [[
  743. local context = {
  744. o = {
  745. commentstring = '-- %s',
  746. undolevels = 0,
  747. virtualedit = 'all',
  748. winblend = 75,
  749. langmap = 'ab,ba',
  750. },
  751. }
  752. local before = get_state()
  753. local inner = vim._with(context, function()
  754. assert(api.nvim_get_current_buf() == cur_buf)
  755. assert(api.nvim_get_current_win() == cur_win)
  756. return get_state()
  757. end)
  758. return { before = before, inner = inner, after = get_state() }
  759. ]]
  760. -- Options in context are set with `vim.o`, so usually both local
  761. -- and global values are affected. Yet all of them should be later
  762. -- restored to pre-context values.
  763. eq({
  764. bo = { cms_cur = '-- %s', cms_other = '## %s', ul_cur = -123456, ul_other = 100 },
  765. wo = { ve_cur = 'all', ve_other = 'block', winbl_cur = 75, winbl_other = 10 },
  766. go = { cms = '-- %s', ul = 0, ve = 'all', winbl = 75, lmap = 'ab,ba' },
  767. }, out.inner)
  768. eq(out.before, out.after)
  769. end)
  770. it('sets options in `buf` context', function()
  771. local out = exec_lua [[
  772. local context = { buf = other_buf, o = { commentstring = '-- %s', undolevels = 0 } }
  773. local before = get_state()
  774. local inner = vim._with(context, function()
  775. assert(api.nvim_get_current_buf() == other_buf)
  776. return get_state()
  777. end)
  778. return { before = before, inner = inner, after = get_state() }
  779. ]]
  780. eq({
  781. bo = { cms_cur = '// %s', cms_other = '-- %s', ul_cur = 250, ul_other = -123456 },
  782. wo = { ve_cur = 'insert', ve_other = 'block', winbl_cur = 25, winbl_other = 10 },
  783. -- Global `winbl` inside context ideally should be untouched and equal
  784. -- to 50. It seems to be equal to 0 because `context.buf` uses
  785. -- `aucmd_prepbuf` C approach which has no guarantees about window or
  786. -- window option values inside context.
  787. go = { cms = '-- %s', ul = 0, ve = 'none', winbl = 0, lmap = 'xy,yx' },
  788. }, out.inner)
  789. eq(out.before, out.after)
  790. end)
  791. it('sets options in `win` context', function()
  792. local out = exec_lua [[
  793. local context = { win = other_win, o = { winblend = 75, virtualedit = 'all' } }
  794. local before = get_state()
  795. local inner = vim._with(context, function()
  796. assert(api.nvim_get_current_win() == other_win)
  797. return get_state()
  798. end)
  799. return { before = before, inner = inner, after = get_state() }
  800. ]]
  801. eq({
  802. bo = { cms_cur = '// %s', cms_other = '## %s', ul_cur = 250, ul_other = 100 },
  803. wo = { winbl_cur = 25, winbl_other = 75, ve_cur = 'insert', ve_other = 'all' },
  804. go = { cms = '$$ %s', ul = 500, winbl = 75, ve = 'all', lmap = 'xy,yx' },
  805. }, out.inner)
  806. eq(out.before, out.after)
  807. end)
  808. it('restores only options from context', function()
  809. local out = exec_lua [[
  810. local context = { o = { undolevels = 0, winblend = 75, langmap = 'ab,ba' } }
  811. local inner = vim._with(context, function()
  812. assert(api.nvim_get_current_buf() == cur_buf)
  813. assert(api.nvim_get_current_win() == cur_win)
  814. vim.o.commentstring = '!! %s'
  815. vim.o.undolevels = 750
  816. vim.o.virtualedit = 'onemore'
  817. vim.o.winblend = 99
  818. vim.o.langmap = 'uv,vu'
  819. return get_state()
  820. end)
  821. return { inner = inner, after = get_state() }
  822. ]]
  823. eq({
  824. bo = { cms_cur = '!! %s', cms_other = '## %s', ul_cur = -123456, ul_other = 100 },
  825. wo = { ve_cur = 'onemore', ve_other = 'block', winbl_cur = 99, winbl_other = 10 },
  826. go = { cms = '!! %s', ul = 750, ve = 'onemore', winbl = 99, lmap = 'uv,vu' },
  827. }, out.inner)
  828. eq({
  829. bo = { cms_cur = '!! %s', cms_other = '## %s', ul_cur = 250, ul_other = 100 },
  830. wo = { ve_cur = 'onemore', ve_other = 'block', winbl_cur = 25, winbl_other = 10 },
  831. go = { cms = '!! %s', ul = 500, ve = 'onemore', winbl = 50, lmap = 'xy,yx' },
  832. }, out.after)
  833. end)
  834. it('does not trigger events', function()
  835. exec_lua [[
  836. _G.test_events = {
  837. 'BufEnter', 'BufLeave', 'WinEnter', 'WinLeave', 'BufWinEnter', 'BufWinLeave'
  838. }
  839. _G.test_context = { o = { undolevels = 0, winblend = 75, langmap = 'ab,ba' } }
  840. _G.test_trig_event = function() vim.cmd.new() end
  841. ]]
  842. assert_events_trigger()
  843. end)
  844. it('can be nested', function()
  845. local out = exec_lua [[
  846. local before, before_inner, after_inner = get_state(), nil, nil
  847. local cxt_o = { commentstring = '-- %s', winblend = 75, langmap = 'ab,ba', undolevels = 0 }
  848. vim._with({ o = cxt_o }, function()
  849. before_inner = get_state()
  850. local inner_cxt_o = { commentstring = '!! %s', winblend = 99, langmap = 'uv,vu' }
  851. inner = vim._with({ o = inner_cxt_o }, get_state)
  852. after_inner = get_state()
  853. end)
  854. return {
  855. before = before, before_inner = before_inner,
  856. inner = inner,
  857. after_inner = after_inner, after = get_state(),
  858. }
  859. ]]
  860. eq('!! %s', out.inner.bo.cms_cur)
  861. eq(99, out.inner.wo.winbl_cur)
  862. eq('uv,vu', out.inner.go.lmap)
  863. eq(0, out.inner.go.ul)
  864. eq(out.before_inner, out.after_inner)
  865. eq(out.before, out.after)
  866. end)
  867. end)
  868. describe('`sandbox` context', function()
  869. it('works', function()
  870. local ok, err = pcall(
  871. exec_lua,
  872. [[
  873. -- Should work as `vim.cmd('sandbox call append(0, "aaa")')`
  874. vim._with({ sandbox = true }, function()
  875. fn.append(0, 'aaa')
  876. end)
  877. ]]
  878. )
  879. eq(false, ok)
  880. matches('Not allowed in sandbox', err)
  881. end)
  882. it('can NOT be nested', function()
  883. -- This behavior is intentionally different from other flags as allowing
  884. -- disabling `sandbox` from nested function seems to be against the point
  885. -- of using `sandbox` context in the first place
  886. local ok, err = pcall(
  887. exec_lua,
  888. [[
  889. vim._with({ sandbox = true }, function()
  890. vim._with({ sandbox = false }, function()
  891. fn.append(0, 'aaa')
  892. end)
  893. end)
  894. ]]
  895. )
  896. eq(false, ok)
  897. matches('Not allowed in sandbox', err)
  898. end)
  899. end)
  900. describe('`silent` context', function()
  901. it('works', function()
  902. exec_lua [[
  903. -- Should be same as `vim.cmd('silent lua print("aaa")')`
  904. vim._with({ silent = true }, function() print('aaa') end)
  905. ]]
  906. eq('', exec_capture('messages'))
  907. exec_lua [[ vim._with({ silent = true }, function() vim.cmd.echomsg('"bbb"') end) ]]
  908. eq('', exec_capture('messages'))
  909. local screen = Screen.new(20, 5)
  910. screen:set_default_attr_ids {
  911. [1] = { bold = true, reverse = true },
  912. [2] = { bold = true, foreground = Screen.colors.Blue },
  913. }
  914. exec_lua [[ vim._with({ silent = true }, function() vim.cmd.echo('"ccc"') end) ]]
  915. screen:expect [[
  916. ^ |
  917. {2:~ }|*3
  918. |
  919. ]]
  920. end)
  921. pending('can be nested', function()
  922. exec_lua [[ vim._with({ silent = true }, function()
  923. vim._with({ silent = false }, function()
  924. print('aaa')
  925. end)
  926. end)]]
  927. eq('aaa', exec_capture('messages'))
  928. end)
  929. end)
  930. describe('`unsilent` context', function()
  931. it('works', function()
  932. exec_lua [[
  933. _G.f = function()
  934. -- Should be same as `vim.cmd('unsilent lua print("aaa")')`
  935. vim._with({ unsilent = true }, function() print('aaa') end)
  936. end
  937. ]]
  938. command('silent lua f()')
  939. eq('aaa', exec_capture('messages'))
  940. end)
  941. pending('can be nested', function()
  942. exec_lua [[
  943. _G.f = function()
  944. vim._with({ unsilent = true }, function()
  945. vim._with({ unsilent = false }, function() print('aaa') end)
  946. end)
  947. end
  948. ]]
  949. command('silent lua f()')
  950. eq('', exec_capture('messages'))
  951. end)
  952. end)
  953. describe('`win` context', function()
  954. it('works', function()
  955. local out = exec_lua [[
  956. local other_win, cur_win = setup_windows()
  957. local inner = vim._with({ win = other_win }, function()
  958. return api.nvim_get_current_win()
  959. end)
  960. return { inner == other_win, api.nvim_get_current_win() == cur_win }
  961. ]]
  962. eq({ true, true }, out)
  963. end)
  964. it('does not trigger events', function()
  965. exec_lua [[
  966. _G.test_events = { 'WinEnter', 'WinLeave', 'BufWinEnter', 'BufWinLeave' }
  967. _G.test_context = { win = other_win }
  968. _G.test_trig_event = function() vim.cmd.new() end
  969. ]]
  970. assert_events_trigger()
  971. end)
  972. it('can access window options', function()
  973. local out = exec_lua [[
  974. local other_win, cur_win = setup_windows()
  975. vim.wo[other_win].winblend = 10
  976. vim.wo[cur_win].winblend = 25
  977. vim._with({ win = other_win }, function()
  978. vim.cmd.setlocal('winblend=0')
  979. end)
  980. return vim.wo[other_win].winblend == 0 and vim.wo[cur_win].winblend == 25
  981. ]]
  982. eq(true, out)
  983. end)
  984. it('works with different kinds of windows', function()
  985. exec_lua [[
  986. local assert_win = function(win)
  987. vim._with({ win = win }, function()
  988. assert(api.nvim_get_current_win() == win)
  989. end)
  990. end
  991. -- Current
  992. assert_win(api.nvim_get_current_win())
  993. -- Not visible
  994. local other_win, cur_win = setup_windows()
  995. vim.cmd.tabnew()
  996. assert_win(other_win)
  997. -- Floating
  998. local float_win = api.nvim_open_win(
  999. api.nvim_create_buf(false, true),
  1000. false,
  1001. { relative = 'editor', row = 1, col = 1, height = 5, width = 5}
  1002. )
  1003. assert_win(float_win)
  1004. ]]
  1005. end)
  1006. it('does not cause ml_get errors with invalid visual selection', function()
  1007. exec_lua [[
  1008. local feedkeys = function(keys) api.nvim_feedkeys(vim.keycode(keys), 'txn', false) end
  1009. -- Add lines to the current buffer and make another window looking into an empty buffer.
  1010. local win_empty, win_lines = setup_windows()
  1011. api.nvim_buf_set_lines(0, 0, -1, true, { 'a', 'b', 'c' })
  1012. -- Start Visual in current window, redraw in other window with fewer lines.
  1013. -- Should be fixed by vim-patch:8.2.4018.
  1014. feedkeys('G<C-V>')
  1015. vim._with({ win = win_empty }, function() vim.cmd.redraw() end)
  1016. -- Start Visual in current window, extend it in other window with more lines.
  1017. -- Fixed for win_execute by vim-patch:8.2.4026, but nvim_win_call should also not be affected.
  1018. feedkeys('<Esc>gg')
  1019. api.nvim_set_current_win(win_empty)
  1020. feedkeys('gg<C-V>')
  1021. vim._with({ win = win_lines }, function() feedkeys('G<C-V>') end)
  1022. vim.cmd.redraw()
  1023. ]]
  1024. end)
  1025. it('can be nested', function()
  1026. exec_lua [[
  1027. local other_win, cur_win = setup_windows()
  1028. vim._with({ win = other_win }, function()
  1029. assert(api.nvim_get_current_win() == other_win)
  1030. inner = vim._with({ win = cur_win }, function()
  1031. assert(api.nvim_get_current_win() == cur_win)
  1032. end)
  1033. assert(api.nvim_get_current_win() == other_win)
  1034. end)
  1035. assert(api.nvim_get_current_win() == cur_win)
  1036. ]]
  1037. end)
  1038. it('updates ruler if cursor moved', function()
  1039. local screen = Screen.new(30, 5)
  1040. screen:set_default_attr_ids {
  1041. [1] = { reverse = true },
  1042. [2] = { bold = true, reverse = true },
  1043. }
  1044. exec_lua [[
  1045. vim.opt.ruler = true
  1046. local lines = {}
  1047. for i = 0, 499 do lines[#lines + 1] = tostring(i) end
  1048. api.nvim_buf_set_lines(0, 0, -1, true, lines)
  1049. api.nvim_win_set_cursor(0, { 20, 0 })
  1050. vim.cmd 'split'
  1051. _G.win = api.nvim_get_current_win()
  1052. vim.cmd "wincmd w | redraw"
  1053. ]]
  1054. screen:expect [[
  1055. 19 |
  1056. {1:[No Name] [+] 20,1 3%}|
  1057. ^19 |
  1058. {2:[No Name] [+] 20,1 3%}|
  1059. |
  1060. ]]
  1061. exec_lua [[
  1062. vim._with({ win = win }, function() api.nvim_win_set_cursor(0, { 100, 0 }) end)
  1063. vim.cmd "redraw"
  1064. ]]
  1065. screen:expect [[
  1066. 99 |
  1067. {1:[No Name] [+] 100,1 19%}|
  1068. ^19 |
  1069. {2:[No Name] [+] 20,1 3%}|
  1070. |
  1071. ]]
  1072. end)
  1073. it('layout in current tabpage does not affect windows in others', function()
  1074. command('tab split')
  1075. local t2_move_win = api.nvim_get_current_win()
  1076. command('vsplit')
  1077. local t2_other_win = api.nvim_get_current_win()
  1078. command('tabprevious')
  1079. matches('E36: Not enough room$', pcall_err(command, 'execute "split|"->repeat(&lines)'))
  1080. command('vsplit')
  1081. exec_lua('vim._with({ win = ... }, function() vim.cmd.wincmd "J" end)', t2_move_win)
  1082. eq({ 'col', { { 'leaf', t2_other_win }, { 'leaf', t2_move_win } } }, fn.winlayout(2))
  1083. end)
  1084. end)
  1085. describe('`wo` context', function()
  1086. before_each(function()
  1087. exec_lua [[
  1088. _G.other_win, _G.cur_win = setup_windows()
  1089. -- 'virtualedit' is global or local to window (global-local) and string
  1090. vim.wo[other_win].virtualedit = 'block'
  1091. vim.wo[cur_win].virtualedit = 'insert'
  1092. vim.go.virtualedit = 'none'
  1093. -- 'winblend' is local to window and number
  1094. vim.wo[other_win].winblend = 10
  1095. vim.wo[cur_win].winblend = 25
  1096. vim.go.winblend = 50
  1097. _G.get_state = function()
  1098. return {
  1099. wo = {
  1100. ve_cur = vim.wo[cur_win].virtualedit,
  1101. ve_other = vim.wo[other_win].virtualedit,
  1102. winbl_cur = vim.wo[cur_win].winblend,
  1103. winbl_other = vim.wo[other_win].winblend,
  1104. },
  1105. go = {
  1106. ve = vim.go.virtualedit,
  1107. winbl = vim.go.winblend,
  1108. },
  1109. }
  1110. end
  1111. ]]
  1112. end)
  1113. it('works', function()
  1114. local out = exec_lua [[
  1115. local context = { wo = { virtualedit = 'all', winblend = 75 } }
  1116. local before = get_state()
  1117. local inner = vim._with(context, function()
  1118. assert(api.nvim_get_current_win() == cur_win)
  1119. return get_state()
  1120. end)
  1121. return { before = before, inner = inner, after = get_state() }
  1122. ]]
  1123. eq({
  1124. wo = { ve_cur = 'all', ve_other = 'block', winbl_cur = 75, winbl_other = 10 },
  1125. go = { ve = 'none', winbl = 75 },
  1126. }, out.inner)
  1127. eq(out.before, out.after)
  1128. end)
  1129. it('sets options in `win` context', function()
  1130. local out = exec_lua [[
  1131. local context = { win = other_win, wo = { virtualedit = 'all', winblend = 75 } }
  1132. local before = get_state()
  1133. local inner = vim._with(context, function()
  1134. assert(api.nvim_get_current_win() == other_win)
  1135. return get_state()
  1136. end)
  1137. return { before = before, inner = inner, after = get_state() }
  1138. ]]
  1139. eq({
  1140. wo = { ve_cur = 'insert', ve_other = 'all', winbl_cur = 25, winbl_other = 75 },
  1141. go = { ve = 'none', winbl = 75 },
  1142. }, out.inner)
  1143. eq(out.before, out.after)
  1144. end)
  1145. it('restores only options from context', function()
  1146. local out = exec_lua [[
  1147. local context = { wo = { winblend = 75 } }
  1148. local inner = vim._with(context, function()
  1149. assert(api.nvim_get_current_win() == cur_win)
  1150. vim.wo[cur_win].virtualedit = 'onemore'
  1151. vim.wo[cur_win].winblend = 99
  1152. return get_state()
  1153. end)
  1154. return { inner = inner, after = get_state() }
  1155. ]]
  1156. eq({
  1157. wo = { ve_cur = 'onemore', ve_other = 'block', winbl_cur = 99, winbl_other = 10 },
  1158. go = { ve = 'none', winbl = 99 },
  1159. }, out.inner)
  1160. eq({
  1161. wo = { ve_cur = 'onemore', ve_other = 'block', winbl_cur = 25, winbl_other = 10 },
  1162. go = { ve = 'none', winbl = 50 },
  1163. }, out.after)
  1164. end)
  1165. it('does not trigger events', function()
  1166. exec_lua [[
  1167. _G.test_events = { 'WinEnter', 'WinLeave', 'BufWinEnter', 'BufWinLeave' }
  1168. _G.test_context = { wo = { winblend = 75 } }
  1169. _G.test_trig_event = function() vim.cmd.new() end
  1170. ]]
  1171. assert_events_trigger()
  1172. end)
  1173. it('can be nested', function()
  1174. local out = exec_lua [[
  1175. local before, before_inner, after_inner = get_state(), nil, nil
  1176. vim._with({ wo = { winblend = 75, virtualedit = 'all' } }, function()
  1177. before_inner = get_state()
  1178. inner = vim._with({ wo = { winblend = 99 } }, get_state)
  1179. after_inner = get_state()
  1180. end)
  1181. return {
  1182. before = before, before_inner = before_inner,
  1183. inner = inner,
  1184. after_inner = after_inner, after = get_state(),
  1185. }
  1186. ]]
  1187. eq(99, out.inner.wo.winbl_cur)
  1188. eq('all', out.inner.wo.ve_cur)
  1189. eq(out.before_inner, out.after_inner)
  1190. eq(out.before, out.after)
  1191. end)
  1192. end)
  1193. it('returns what callback returns', function()
  1194. local out_verify = exec_lua [[
  1195. out = { vim._with({}, function()
  1196. return 'a', 2, nil, { 4 }, function() end
  1197. end) }
  1198. return {
  1199. out[1] == 'a', out[2] == 2, out[3] == nil,
  1200. vim.deep_equal(out[4], { 4 }),
  1201. type(out[5]) == 'function',
  1202. vim.tbl_count(out),
  1203. }
  1204. ]]
  1205. eq({ true, true, true, true, true, 4 }, out_verify)
  1206. end)
  1207. it('can return values by reference', function()
  1208. local out = exec_lua [[
  1209. local val = { 4, 10 }
  1210. local ref = vim._with({}, function() return val end)
  1211. ref[1] = 7
  1212. return val
  1213. ]]
  1214. eq({ 7, 10 }, out)
  1215. end)
  1216. it('can not work with conflicting `buf` and `win`', function()
  1217. local out = exec_lua [[
  1218. local other_buf, cur_buf = setup_buffers()
  1219. local other_win, cur_win = setup_windows()
  1220. assert(api.nvim_win_get_buf(other_win) ~= other_buf)
  1221. local _, err = pcall(vim._with, { buf = other_buf, win = other_win }, function() end)
  1222. return err
  1223. ]]
  1224. matches('Can not set both `buf` and `win`', out)
  1225. end)
  1226. it('works with several contexts at once', function()
  1227. local out = exec_lua [[
  1228. local other_buf, cur_buf = setup_buffers()
  1229. vim.bo[other_buf].commentstring = '## %s'
  1230. api.nvim_buf_set_lines(other_buf, 0, -1, false, { 'aaa', 'bbb', 'ccc' })
  1231. api.nvim_buf_set_mark(other_buf, 'm', 2, 2, {})
  1232. vim.go.commentstring = '// %s'
  1233. vim.go.langmap = 'xy,yx'
  1234. local context = {
  1235. buf = other_buf,
  1236. bo = { commentstring = '-- %s' },
  1237. go = { langmap = 'ab,ba' },
  1238. lockmarks = true,
  1239. }
  1240. local inner = vim._with(context, function()
  1241. api.nvim_buf_set_lines(0, 0, -1, false, { 'uuu', 'vvv', 'www' })
  1242. return {
  1243. buf = api.nvim_get_current_buf(),
  1244. bo = { cms = vim.bo.commentstring },
  1245. go = { cms = vim.go.commentstring, lmap = vim.go.langmap },
  1246. mark = api.nvim_buf_get_mark(0, 'm')
  1247. }
  1248. end)
  1249. local after = {
  1250. buf = api.nvim_get_current_buf(),
  1251. bo = { cms = vim.bo[other_buf].commentstring },
  1252. go = { cms = vim.go.commentstring, lmap = vim.go.langmap },
  1253. mark = api.nvim_buf_get_mark(other_buf, 'm')
  1254. }
  1255. return {
  1256. context_buf = other_buf, cur_buf = cur_buf,
  1257. inner = inner, after = after
  1258. }
  1259. ]]
  1260. eq({
  1261. buf = out.context_buf,
  1262. bo = { cms = '-- %s' },
  1263. go = { cms = '// %s', lmap = 'ab,ba' },
  1264. mark = { 2, 2 },
  1265. }, out.inner)
  1266. eq({
  1267. buf = out.cur_buf,
  1268. bo = { cms = '## %s' },
  1269. go = { cms = '// %s', lmap = 'xy,yx' },
  1270. mark = { 2, 2 },
  1271. }, out.after)
  1272. end)
  1273. it('works with same option set in different contexts', function()
  1274. local out = exec_lua [[
  1275. local get_state = function()
  1276. return {
  1277. bo = { cms = vim.bo.commentstring },
  1278. wo = { ve = vim.wo.virtualedit },
  1279. go = { cms = vim.go.commentstring, ve = vim.go.virtualedit },
  1280. }
  1281. end
  1282. vim.bo.commentstring = '// %s'
  1283. vim.go.commentstring = '$$ %s'
  1284. vim.wo.virtualedit = 'insert'
  1285. vim.go.virtualedit = 'none'
  1286. local before = get_state()
  1287. local context_no_go = {
  1288. o = { commentstring = '-- %s', virtualedit = 'all' },
  1289. bo = { commentstring = '!! %s' },
  1290. wo = { virtualedit = 'onemore' },
  1291. }
  1292. local inner_no_go = vim._with(context_no_go, get_state)
  1293. local middle = get_state()
  1294. local context_with_go = {
  1295. o = { commentstring = '-- %s', virtualedit = 'all' },
  1296. bo = { commentstring = '!! %s' },
  1297. wo = { virtualedit = 'onemore' },
  1298. go = { commentstring = '@@ %s', virtualedit = 'block' },
  1299. }
  1300. local inner_with_go = vim._with(context_with_go, get_state)
  1301. return {
  1302. before = before,
  1303. inner_no_go = inner_no_go,
  1304. middle = middle,
  1305. inner_with_go = inner_with_go,
  1306. after = get_state(),
  1307. }
  1308. ]]
  1309. -- Should prefer explicit local scopes instead of `o`
  1310. eq({
  1311. bo = { cms = '!! %s' },
  1312. wo = { ve = 'onemore' },
  1313. go = { cms = '-- %s', ve = 'all' },
  1314. }, out.inner_no_go)
  1315. eq(out.before, out.middle)
  1316. -- Should prefer explicit global scopes instead of `o`
  1317. eq({
  1318. bo = { cms = '!! %s' },
  1319. wo = { ve = 'onemore' },
  1320. go = { cms = '@@ %s', ve = 'block' },
  1321. }, out.inner_with_go)
  1322. eq(out.middle, out.after)
  1323. end)
  1324. pending('can forward command modifiers to user command', function()
  1325. local out = exec_lua [[
  1326. local test_flags = {
  1327. 'emsg_silent',
  1328. 'hide',
  1329. 'keepalt',
  1330. 'keepjumps',
  1331. 'keepmarks',
  1332. 'keeppatterns',
  1333. 'lockmarks',
  1334. 'noautocmd',
  1335. 'silent',
  1336. 'unsilent',
  1337. }
  1338. local used_smods
  1339. local command = function(data)
  1340. used_smods = data.smods
  1341. end
  1342. api.nvim_create_user_command('DummyLog', command, {})
  1343. local res = {}
  1344. for _, flag in ipairs(test_flags) do
  1345. used_smods = nil
  1346. vim._with({ [flag] = true }, function() vim.cmd('DummyLog') end)
  1347. res[flag] = used_smods[flag]
  1348. end
  1349. return res
  1350. ]]
  1351. for k, v in pairs(out) do
  1352. eq({ k, true }, { k, v })
  1353. end
  1354. end)
  1355. it('handles error in callback', function()
  1356. -- Should still restore initial context
  1357. local out_buf = exec_lua [[
  1358. local other_buf, cur_buf = setup_buffers()
  1359. vim.bo[other_buf].commentstring = '## %s'
  1360. local context = { buf = other_buf, bo = { commentstring = '-- %s' } }
  1361. local ok, err = pcall(vim._with, context, function() error('Oops buf', 0) end)
  1362. return {
  1363. ok,
  1364. err,
  1365. api.nvim_get_current_buf() == cur_buf,
  1366. vim.bo[other_buf].commentstring,
  1367. }
  1368. ]]
  1369. eq({ false, 'Oops buf', true, '## %s' }, out_buf)
  1370. local out_win = exec_lua [[
  1371. local other_win, cur_win = setup_windows()
  1372. vim.wo[other_win].winblend = 25
  1373. local context = { win = other_win, wo = { winblend = 50 } }
  1374. local ok, err = pcall(vim._with, context, function() error('Oops win', 0) end)
  1375. return {
  1376. ok,
  1377. err,
  1378. api.nvim_get_current_win() == cur_win,
  1379. vim.wo[other_win].winblend,
  1380. }
  1381. ]]
  1382. eq({ false, 'Oops win', true, 25 }, out_win)
  1383. end)
  1384. it('handles not supported option', function()
  1385. local out = exec_lua [[
  1386. -- Should still restore initial state
  1387. vim.bo.commentstring = '## %s'
  1388. local context = { o = { commentstring = '-- %s' }, bo = { winblend = 10 } }
  1389. local ok, err = pcall(vim._with, context, function() end)
  1390. return { ok = ok, err = err, cms = vim.bo.commentstring }
  1391. ]]
  1392. eq(false, out.ok)
  1393. matches('window.*option.*winblend', out.err)
  1394. eq('## %s', out.cms)
  1395. end)
  1396. it('validates arguments', function()
  1397. exec_lua [[
  1398. _G.get_error = function(...)
  1399. local _, err = pcall(vim._with, ...)
  1400. return err or ''
  1401. end
  1402. ]]
  1403. local get_error = function(string_args)
  1404. return exec_lua('return get_error(' .. string_args .. ')')
  1405. end
  1406. matches('context.*table', get_error("'a', function() end"))
  1407. matches('f.*function', get_error('{}, 1'))
  1408. local assert_context = function(bad_context, expected_type)
  1409. local bad_field = vim.tbl_keys(bad_context)[1]
  1410. matches(
  1411. 'context%.' .. bad_field .. '.*' .. expected_type,
  1412. get_error(vim.inspect(bad_context) .. ', function() end')
  1413. )
  1414. end
  1415. assert_context({ bo = 1 }, 'table')
  1416. assert_context({ buf = 'a' }, 'number')
  1417. assert_context({ emsg_silent = 1 }, 'boolean')
  1418. assert_context({ env = 1 }, 'table')
  1419. assert_context({ go = 1 }, 'table')
  1420. assert_context({ hide = 1 }, 'boolean')
  1421. assert_context({ keepalt = 1 }, 'boolean')
  1422. assert_context({ keepjumps = 1 }, 'boolean')
  1423. assert_context({ keepmarks = 1 }, 'boolean')
  1424. assert_context({ keeppatterns = 1 }, 'boolean')
  1425. assert_context({ lockmarks = 1 }, 'boolean')
  1426. assert_context({ noautocmd = 1 }, 'boolean')
  1427. assert_context({ o = 1 }, 'table')
  1428. assert_context({ sandbox = 1 }, 'boolean')
  1429. assert_context({ silent = 1 }, 'boolean')
  1430. assert_context({ unsilent = 1 }, 'boolean')
  1431. assert_context({ win = 'a' }, 'number')
  1432. assert_context({ wo = 1 }, 'table')
  1433. matches('Invalid buffer', get_error('{ buf = -1 }, function() end'))
  1434. matches('Invalid window', get_error('{ win = -1 }, function() end'))
  1435. end)
  1436. end)