aboutsummaryrefslogtreecommitdiff
path: root/test/functional/lua/with_spec.lua
diff options
context:
space:
mode:
authordundargoc <gocdundar@gmail.com>2024-06-03 19:04:28 +0200
committerdundargoc <33953936+dundargoc@users.noreply.github.com>2024-06-08 21:38:06 +0200
commit9afa1fd35510c5fe485f4a1dfdabf94e5f051a1c (patch)
tree0a9d7b503db73886b848e4c653119a741d4bf113 /test/functional/lua/with_spec.lua
parent4881211097aba7f5d17700362ec0967e3024f074 (diff)
downloadrneovim-9afa1fd35510c5fe485f4a1dfdabf94e5f051a1c.tar.gz
rneovim-9afa1fd35510c5fe485f4a1dfdabf94e5f051a1c.tar.bz2
rneovim-9afa1fd35510c5fe485f4a1dfdabf94e5f051a1c.zip
feat(lua): add `vim._with`
It's a function to perform operations in their own sealed context, similar to pythons `with`. This helps ease operations where you need to perform an operation in a specific context, and then restore the context. Marked as private for now as it's not ready for public use. The current plan is to start using this internally so we can discover and fix any problems. Once this is ready to be exposed it will be renamed to `vim.with`. Usage: ```lua local ret = vim._with({context = val}, function() return "hello" end) ``` , where `context` is any combination of: - `buf` - `emsg_silent` - `hide` - `horizontal` - `keepalt` - `keepjumps` - `keepmarks` - `keeppatterns` - `lockmarks` - `noautocmd` - `options` - `sandbox` - `silent` - `unsilent` - `win` (except for `win` and `buf` which can't be used at the same time). This list will most likely be expanded in the future. Work on https://github.com/neovim/neovim/issues/19832. Co-authored-by: Lewis Russell <lewis6991@gmail.com>
Diffstat (limited to 'test/functional/lua/with_spec.lua')
-rw-r--r--test/functional/lua/with_spec.lua292
1 files changed, 292 insertions, 0 deletions
diff --git a/test/functional/lua/with_spec.lua b/test/functional/lua/with_spec.lua
new file mode 100644
index 0000000000..36dee9630a
--- /dev/null
+++ b/test/functional/lua/with_spec.lua
@@ -0,0 +1,292 @@
+local t = require('test.testutil')
+local n = require('test.functional.testnvim')()
+local Screen = require('test.functional.ui.screen')
+
+local fn = n.fn
+local api = n.api
+local command = n.command
+local eq = t.eq
+local exec_lua = n.exec_lua
+local matches = t.matches
+local pcall_err = t.pcall_err
+
+before_each(function()
+ n.clear()
+end)
+
+describe('vim._with {buf = }', function()
+ it('does not trigger autocmd', function()
+ exec_lua [[
+ local new = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_create_autocmd( { 'BufEnter', 'BufLeave', 'BufWinEnter', 'BufWinLeave' }, {
+ callback = function() _G.n = (_G.n or 0) + 1 end
+ })
+ vim._with({buf = new}, function()
+ end)
+ assert(_G.n == nil)
+ ]]
+ end)
+
+ it('trigger autocmd if changed within context', function()
+ exec_lua [[
+ local new = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_create_autocmd( { 'BufEnter', 'BufLeave', 'BufWinEnter', 'BufWinLeave' }, {
+ callback = function() _G.n = (_G.n or 0) + 1 end
+ })
+ vim._with({}, function()
+ vim.api.nvim_set_current_buf(new)
+ assert(_G.n ~= nil)
+ end)
+ ]]
+ end)
+
+ it('can access buf options', function()
+ local buf1 = api.nvim_get_current_buf()
+ local buf2 = exec_lua [[
+ buf2 = vim.api.nvim_create_buf(false, true)
+ return buf2
+ ]]
+
+ eq(false, api.nvim_get_option_value('autoindent', { buf = buf1 }))
+ eq(false, api.nvim_get_option_value('autoindent', { buf = buf2 }))
+
+ local val = exec_lua [[
+ return vim._with({buf = buf2}, function()
+ vim.cmd "set autoindent"
+ return vim.api.nvim_get_current_buf()
+ end)
+ ]]
+
+ eq(false, api.nvim_get_option_value('autoindent', { buf = buf1 }))
+ eq(true, api.nvim_get_option_value('autoindent', { buf = buf2 }))
+ eq(buf1, api.nvim_get_current_buf())
+ eq(buf2, val)
+ end)
+
+ it('does not cause ml_get errors with invalid visual selection', function()
+ exec_lua [[
+ local api = vim.api
+ local t = function(s) return api.nvim_replace_termcodes(s, true, true, true) end
+ api.nvim_buf_set_lines(0, 0, -1, true, {"a", "b", "c"})
+ api.nvim_feedkeys(t "G<C-V>", "txn", false)
+ vim._with({buf = api.nvim_create_buf(false, true)}, function() vim.cmd "redraw" end)
+ ]]
+ end)
+
+ it('can be nested crazily with hidden buffers', function()
+ eq(
+ true,
+ exec_lua([[
+ local function scratch_buf_call(fn)
+ local buf = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_set_option_value('cindent', true, {buf = buf})
+ return vim._with({buf = buf}, function()
+ return vim.api.nvim_get_current_buf() == buf
+ and vim.api.nvim_get_option_value('cindent', {buf = buf})
+ and fn()
+ end) and vim.api.nvim_buf_delete(buf, {}) == nil
+ end
+
+ return scratch_buf_call(function()
+ return scratch_buf_call(function()
+ return scratch_buf_call(function()
+ return scratch_buf_call(function()
+ return scratch_buf_call(function()
+ return scratch_buf_call(function()
+ return scratch_buf_call(function()
+ return scratch_buf_call(function()
+ return scratch_buf_call(function()
+ return scratch_buf_call(function()
+ return scratch_buf_call(function()
+ return scratch_buf_call(function()
+ return true
+ end)
+ end)
+ end)
+ end)
+ end)
+ end)
+ end)
+ end)
+ end)
+ end)
+ end)
+ end)
+ ]])
+ )
+ end)
+
+ it('can return values by reference', function()
+ eq(
+ { 4, 7 },
+ exec_lua [[
+ local val = {4, 10}
+ local ref = vim._with({ buf = 0}, function() return val end)
+ ref[2] = 7
+ return val
+ ]]
+ )
+ end)
+end)
+
+describe('vim._with {win = }', function()
+ it('does not trigger autocmd', function()
+ exec_lua [[
+ local old = vim.api.nvim_get_current_win()
+ vim.cmd("new")
+ local new = vim.api.nvim_get_current_win()
+ vim.api.nvim_create_autocmd( { 'WinEnter', 'WinLeave' }, {
+ callback = function() _G.n = (_G.n or 0) + 1 end
+ })
+ vim._with({win = old}, function()
+ end)
+ assert(_G.n == nil)
+ ]]
+ end)
+
+ it('trigger autocmd if changed within context', function()
+ exec_lua [[
+ local old = vim.api.nvim_get_current_win()
+ vim.cmd("new")
+ local new = vim.api.nvim_get_current_win()
+ vim.api.nvim_create_autocmd( { 'WinEnter', 'WinLeave' }, {
+ callback = function() _G.n = (_G.n or 0) + 1 end
+ })
+ vim._with({}, function()
+ vim.api.nvim_set_current_win(old)
+ assert(_G.n ~= nil)
+ end)
+ ]]
+ end)
+
+ it('can access window options', function()
+ command('vsplit')
+ local win1 = api.nvim_get_current_win()
+ command('wincmd w')
+ local win2 = exec_lua [[
+ win2 = vim.api.nvim_get_current_win()
+ return win2
+ ]]
+ command('wincmd p')
+
+ eq('', api.nvim_get_option_value('winhighlight', { win = win1 }))
+ eq('', api.nvim_get_option_value('winhighlight', { win = win2 }))
+
+ local val = exec_lua [[
+ return vim._with({win = win2}, function()
+ vim.cmd "setlocal winhighlight=Normal:Normal"
+ return vim.api.nvim_get_current_win()
+ end)
+ ]]
+
+ eq('', api.nvim_get_option_value('winhighlight', { win = win1 }))
+ eq('Normal:Normal', api.nvim_get_option_value('winhighlight', { win = win2 }))
+ eq(win1, api.nvim_get_current_win())
+ eq(win2, val)
+ end)
+
+ it('does not cause ml_get errors with invalid visual selection', function()
+ -- Add lines to the current buffer and make another window looking into an empty buffer.
+ exec_lua [[
+ _G.api = vim.api
+ _G.t = function(s) return api.nvim_replace_termcodes(s, true, true, true) end
+ _G.win_lines = api.nvim_get_current_win()
+ vim.cmd "new"
+ _G.win_empty = api.nvim_get_current_win()
+ api.nvim_set_current_win(win_lines)
+ api.nvim_buf_set_lines(0, 0, -1, true, {"a", "b", "c"})
+ ]]
+
+ -- Start Visual in current window, redraw in other window with fewer lines.
+ exec_lua [[
+ api.nvim_feedkeys(t "G<C-V>", "txn", false)
+ vim._with({win = win_empty}, function() vim.cmd "redraw" end)
+ ]]
+
+ -- Start Visual in current window, extend it in other window with more lines.
+ exec_lua [[
+ api.nvim_feedkeys(t "<Esc>gg", "txn", false)
+ api.nvim_set_current_win(win_empty)
+ api.nvim_feedkeys(t "gg<C-V>", "txn", false)
+ vim._with({win = win_lines}, function() api.nvim_feedkeys(t "G<C-V>", "txn", false) end)
+ vim.cmd "redraw"
+ ]]
+ end)
+
+ it('updates ruler if cursor moved', function()
+ local screen = Screen.new(30, 5)
+ screen:set_default_attr_ids {
+ [1] = { reverse = true },
+ [2] = { bold = true, reverse = true },
+ }
+ screen:attach()
+ exec_lua [[
+ _G.api = vim.api
+ vim.opt.ruler = true
+ local lines = {}
+ for i = 0, 499 do lines[#lines + 1] = tostring(i) end
+ api.nvim_buf_set_lines(0, 0, -1, true, lines)
+ api.nvim_win_set_cursor(0, {20, 0})
+ vim.cmd "split"
+ _G.win = api.nvim_get_current_win()
+ vim.cmd "wincmd w | redraw"
+ ]]
+ screen:expect [[
+ 19 |
+ {1:[No Name] [+] 20,1 3%}|
+ ^19 |
+ {2:[No Name] [+] 20,1 3%}|
+ |
+ ]]
+ exec_lua [[
+ vim._with({win = win}, function() api.nvim_win_set_cursor(0, {100, 0}) end)
+ vim.cmd "redraw"
+ ]]
+ screen:expect [[
+ 99 |
+ {1:[No Name] [+] 100,1 19%}|
+ ^19 |
+ {2:[No Name] [+] 20,1 3%}|
+ |
+ ]]
+ end)
+
+ it('can return values by reference', function()
+ eq(
+ { 7, 10 },
+ exec_lua [[
+ local val = {4, 10}
+ local ref = vim._with({win = 0}, function() return val end)
+ ref[1] = 7
+ return val
+ ]]
+ )
+ end)
+
+ it('layout in current tabpage does not affect windows in others', function()
+ command('tab split')
+ local t2_move_win = api.nvim_get_current_win()
+ command('vsplit')
+ local t2_other_win = api.nvim_get_current_win()
+ command('tabprevious')
+ matches('E36: Not enough room$', pcall_err(command, 'execute "split|"->repeat(&lines)'))
+ command('vsplit')
+
+ exec_lua('vim._with({win = ...}, function() vim.cmd.wincmd "J" end)', t2_move_win)
+ eq({ 'col', { { 'leaf', t2_other_win }, { 'leaf', t2_move_win } } }, fn.winlayout(2))
+ end)
+end)
+
+describe('vim._with {lockmarks = true}', function()
+ it('is reset', function()
+ local mark = exec_lua [[
+ vim.api.nvim_buf_set_lines(0, 0, 0, false, {"marky", "snarky", "malarkey"})
+ vim.api.nvim_buf_set_mark(0,"m",1,0, {})
+ vim._with({lockmarks = true}, function()
+ vim.api.nvim_buf_set_lines(0, 0, 2, false, {"mass", "mess", "moss"})
+ end)
+ return vim.api.nvim_buf_get_mark(0,"m")
+ ]]
+ t.eq(mark, { 1, 0 })
+ end)
+end)