Files
neovim/test/functional/autocmd/markset_spec.lua
Nathan Smith 551bb63d44 feat(events): MarkSet event, aucmd_defer() #35793
Problem:
- Can't subscribe to "mark" events.
- Executing events is risky because they can't be deferred.

Solution:
- Introduce `MarkSet` event.
- Introduce `aucmd_defer()`.

Helped-by: zeertzjq <zeertzjq@outlook.com>
Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
2025-12-07 15:13:31 -05:00

287 lines
6.2 KiB
Lua

local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local api = n.api
local clear = n.clear
local command = n.command
local feed = n.feed
local poke_eventloop = n.poke_eventloop
local eval = n.eval
local eq = t.eq
local neq = t.neq
describe('MarkSet', function()
-- TODO(justinmk): support other marks?: [, ] <, > . ^ " '
before_each(function()
clear()
end)
it('emits when lowercase/uppercase/[/] marks are set', function()
command([[
let g:mark_names = ''
let g:mark_events = []
autocmd MarkSet * call add(g:mark_events, {'event': deepcopy(v:event)}) | let g:mark_names ..= expand('<amatch>')
" TODO: there is a bug lurking here.
" autocmd MarkSet * let g:mark_names ..= expand('<amatch>')
]])
api.nvim_buf_set_lines(0, 0, -1, true, {
'foo\0bar',
'baz text',
'line 3',
})
feed('ma')
feed('j')
command('mark b')
poke_eventloop()
eq('ab', eval('g:mark_names'))
-- event-data is copied to `v:event`.
eq({
{
event = {
col = 0,
line = 1,
name = 'a',
},
},
{
event = {
col = 0,
line = 2,
name = 'b',
},
},
}, eval('g:mark_events'))
feed('mA')
feed('l')
feed('mB')
feed('j')
feed('mC')
feed('x') -- TODO(justinmk): Sets [,] marks but does not emit MarkSet event (yet).
feed('0vll<esc>') -- TODO(justinmk): Sets <,> marks but does not emit MarkSet event (yet).
-- XXX: set these marks manually to exercise these cases.
api.nvim_buf_set_mark(0, '[', 2, 0, {})
api.nvim_buf_set_mark(0, ']', 2, 0, {})
api.nvim_buf_set_mark(0, '<', 2, 0, {})
api.nvim_buf_set_mark(0, '>', 2, 0, {})
api.nvim_buf_set_mark(0, '"', 2, 0, {})
poke_eventloop()
eq('abABC[]<>"', eval('g:mark_names'))
end)
it('can subscribe to specific marks by pattern', function()
command([[
let g:mark_names = ''
autocmd MarkSet [ab] let g:mark_names ..= expand('<amatch>')
]])
api.nvim_buf_set_lines(0, 0, -1, true, {
'foo\0bar',
'baz text',
})
feed('md')
feed('mc')
feed('l')
feed('mb')
feed('j')
feed('ma')
poke_eventloop()
eq('ba', eval('g:mark_names'))
end)
it('handles marks across multiple windows/buffers', function()
local orig_bufnr = api.nvim_get_current_buf()
command('enew')
local second_bufnr = api.nvim_get_current_buf()
api.nvim_buf_set_lines(second_bufnr, 0, -1, true, {
'second buffer line 1',
'second buffer line 2',
})
command('enew')
local third_bufnr = api.nvim_get_current_buf()
api.nvim_buf_set_lines(third_bufnr, 0, -1, true, {
'third buffer line 1',
'third buffer line 2',
})
command('split')
command('vsplit')
command('tabnew')
command('split')
command([[
let g:markset_events = []
autocmd MarkSet * call add(g:markset_events, { 'buf': 0 + expand('<abuf>'), 'event': deepcopy(v:event) })
]])
command('buffer ' .. orig_bufnr)
feed('gg')
feed('mA')
command('wincmd w')
command('tabnext')
feed('mB')
command('wincmd w')
command('enew')
local final_bufnr = api.nvim_get_current_buf()
api.nvim_buf_set_lines(final_bufnr, 0, -1, true, {
'final buffer after chaos',
'line 2 of final buffer',
})
feed('j')
feed('mC')
command('tabclose')
feed('mD')
poke_eventloop()
eq({
{
buf = 1,
event = {
col = 0,
line = 1,
name = 'A',
},
},
{
buf = 2,
event = {
col = 0,
line = 1,
name = 'B',
},
},
{
buf = 4,
event = {
col = 0,
line = 2,
name = 'C',
},
},
{
buf = 3,
event = {
col = 0,
line = 1,
name = 'D',
},
},
}, eval('g:markset_events'))
end)
it('handles an autocommand that calls bwipeout!', function()
api.nvim_buf_set_lines(0, 0, -1, true, {
'line 1',
'line 2',
'line 3',
})
local test_bufnr = api.nvim_get_current_buf()
command("autocmd MarkSet * let g:autocmd ..= expand('<amatch>') | bwipeout!")
command([[let g:autocmd = '']])
feed('ma')
poke_eventloop()
eq('a', eval('g:autocmd'))
eq(false, api.nvim_buf_is_valid(test_bufnr))
local current_bufnr = api.nvim_get_current_buf()
neq(current_bufnr, test_bufnr)
end)
it('when autocommand switches windows and tabs', function()
api.nvim_buf_set_lines(0, 0, -1, true, {
'first buffer line 1',
'first buffer line 2',
'first buffer line 3',
})
local first_bufnr = api.nvim_get_current_buf()
command('split')
command('enew')
api.nvim_buf_set_lines(0, 0, -1, true, {
'second buffer line 1',
'second buffer line 2',
})
local second_bufnr = api.nvim_get_current_buf()
command('tabnew')
api.nvim_buf_set_lines(0, 0, -1, true, {
'third buffer line 1',
'third buffer line 2',
'third buffer line 3',
})
local third_bufnr = api.nvim_get_current_buf()
command([[
let g:markset_events = []
autocmd MarkSet * call add(g:markset_events, {'buf': 0 + expand('<abuf>'), 'event': deepcopy(v:event)}) | wincmd w | tabnext
]])
command('buffer ' .. second_bufnr)
feed('j')
feed('mA')
command('buffer ' .. third_bufnr)
feed('l')
feed('mB')
command('buffer ' .. first_bufnr)
feed('jj')
feed('mC')
poke_eventloop()
eq({
{
buf = 2,
event = {
col = 0,
line = 2,
name = 'A',
},
},
{
buf = 3,
event = {
col = 1,
line = 1,
name = 'B',
},
},
{
buf = 1,
event = {
col = 0,
line = 3,
name = 'C',
},
},
}, eval('g:markset_events'))
eq({ 2, 0 }, api.nvim_buf_get_mark(second_bufnr, 'A'))
eq({ 1, 1 }, api.nvim_buf_get_mark(third_bufnr, 'B'))
eq({ 3, 0 }, api.nvim_buf_get_mark(first_bufnr, 'C'))
end)
end)