Files
neovim/test/functional/autocmd/markset_spec.lua
Ashley Hauck 4b54bca3df feat(events): trigger MarkSet autocmd in :delmarks #39156
Problem:
`api.nvim_buf_del_mark` already emits a `MarkSet` event with `col` and `line` set to 0. However, `:delmarks` currently emits no events.

Solution:
Change `:delmarks` to emit the same `col==line==0` event.
2026-04-19 07:42:08 -04:00

350 lines
7.3 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')
-- delmarks should signal on the buf the mark is set on, not the current buf
command('delmarks A')
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',
},
},
{
buf = 1,
event = {
col = 0,
line = 0,
name = 'A',
},
},
}, 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)
it('emits when marks are deleted', function()
command([[
let g:mark_events = []
autocmd MarkSet * call add(g:mark_events, {'event': deepcopy(v:event)})
]])
api.nvim_buf_set_lines(0, 0, -1, true, {
'line 1',
'line 2',
})
feed('ma')
feed('mb')
command('delmarks a')
-- deleting a second time should not trigger an event
command('delmarks a')
api.nvim_buf_del_mark(0, 'b')
api.nvim_buf_del_mark(0, 'b')
poke_eventloop()
eq({
{
event = {
col = 0,
line = 1,
name = 'a',
},
},
{
event = {
col = 0,
line = 1,
name = 'b',
},
},
{
event = {
col = 0,
line = 0,
name = 'a',
},
},
{
event = {
col = 0,
line = 0,
name = 'b',
},
},
}, eval('g:mark_events'))
end)
end)