with_spec.lua 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642
  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. it('no double-free when called from :filter browse oldfiles #31501', function()
  1437. exec_lua([=[
  1438. vim.api.nvim_create_autocmd('BufEnter', {
  1439. callback = function()
  1440. vim._with({ lockmarks = true }, function() end)
  1441. end,
  1442. })
  1443. vim.cmd([[
  1444. let v:oldfiles = ['Xoldfile']
  1445. call nvim_input('1<CR>')
  1446. noswapfile filter /Xoldfile/ browse oldfiles
  1447. ]])
  1448. ]=])
  1449. n.assert_alive()
  1450. eq('Xoldfile', fn.bufname('%'))
  1451. end)
  1452. end)