diff options
author | dundargoc <gocdundar@gmail.com> | 2024-06-03 19:04:28 +0200 |
---|---|---|
committer | dundargoc <33953936+dundargoc@users.noreply.github.com> | 2024-06-08 21:38:06 +0200 |
commit | 9afa1fd35510c5fe485f4a1dfdabf94e5f051a1c (patch) | |
tree | 0a9d7b503db73886b848e4c653119a741d4bf113 /test/functional/lua/with_spec.lua | |
parent | 4881211097aba7f5d17700362ec0967e3024f074 (diff) | |
download | rneovim-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.lua | 292 |
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) |