neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

autocmd_spec.lua (26086B)


      1 local t = require('test.testutil')
      2 local n = require('test.functional.testnvim')()
      3 local Screen = require('test.functional.ui.screen')
      4 
      5 local assert_visible = n.assert_visible
      6 local assert_alive = n.assert_alive
      7 local dedent = t.dedent
      8 local eq = t.eq
      9 local neq = t.neq
     10 local eval = n.eval
     11 local exec = n.exec
     12 local feed = n.feed
     13 local clear = n.clear
     14 local matches = t.matches
     15 local api = n.api
     16 local pcall_err = t.pcall_err
     17 local fn = n.fn
     18 local expect = n.expect
     19 local command = n.command
     20 local exec_lua = n.exec_lua
     21 local retry = t.retry
     22 local source = n.source
     23 
     24 describe('autocmd', function()
     25  before_each(clear)
     26 
     27  it(':tabnew, :split, :close events order, <afile>', function()
     28    local expected = {
     29      { 'WinLeave', '' },
     30      { 'TabLeave', '' },
     31      { 'WinEnter', '' },
     32      { 'TabNew', 'testfile1' }, -- :tabnew
     33      { 'TabEnter', '' },
     34      { 'BufLeave', '' },
     35      { 'BufEnter', 'testfile1' }, -- :split
     36      { 'WinLeave', 'testfile1' },
     37      { 'WinEnter', 'testfile1' },
     38      { 'WinLeave', 'testfile1' },
     39      { 'WinClosed', '1002' }, -- :close, WinClosed <afile> = window-id
     40      { 'WinEnter', 'testfile1' },
     41      { 'WinLeave', 'testfile1' }, -- :bdelete
     42      { 'WinEnter', 'testfile1' },
     43      { 'BufLeave', 'testfile1' },
     44      { 'BufEnter', 'testfile2' },
     45      { 'WinClosed', '1000' },
     46    }
     47    command('let g:evs = []')
     48    command('autocmd BufEnter * :call add(g:evs, ["BufEnter", expand("<afile>")])')
     49    command('autocmd BufLeave * :call add(g:evs, ["BufLeave", expand("<afile>")])')
     50    command('autocmd TabEnter * :call add(g:evs, ["TabEnter", expand("<afile>")])')
     51    command('autocmd TabLeave * :call add(g:evs, ["TabLeave", expand("<afile>")])')
     52    command('autocmd TabNew   * :call add(g:evs, ["TabNew", expand("<afile>")])')
     53    command('autocmd WinEnter * :call add(g:evs, ["WinEnter", expand("<afile>")])')
     54    command('autocmd WinLeave * :call add(g:evs, ["WinLeave", expand("<afile>")])')
     55    command('autocmd WinClosed * :call add(g:evs, ["WinClosed", expand("<afile>")])')
     56    command('tabnew testfile1')
     57    command('split')
     58    command('close')
     59    command('new testfile2')
     60    command('bdelete 1')
     61    eq(expected, eval('g:evs'))
     62  end)
     63 
     64  it('first edit causes BufUnload on NoName', function()
     65    local expected = {
     66      { 'BufUnload', '' },
     67      { 'BufDelete', '' },
     68      { 'BufWipeout', '' },
     69      { 'BufEnter', 'testfile1' },
     70    }
     71    command('let g:evs = []')
     72    command('autocmd BufEnter * :call add(g:evs, ["BufEnter", expand("<afile>")])')
     73    command('autocmd BufDelete * :call add(g:evs, ["BufDelete", expand("<afile>")])')
     74    command('autocmd BufLeave * :call add(g:evs, ["BufLeave", expand("<afile>")])')
     75    command('autocmd BufUnload * :call add(g:evs, ["BufUnload", expand("<afile>")])')
     76    command('autocmd BufWipeout * :call add(g:evs, ["BufWipeout", expand("<afile>")])')
     77    command('edit testfile1')
     78    eq(expected, eval('g:evs'))
     79  end)
     80 
     81  it('WinClosed is non-recursive', function()
     82    command('let g:triggered = 0')
     83    command('autocmd WinClosed * :let g:triggered+=1 | :bdelete 2')
     84    command('new testfile2')
     85    command('new testfile3')
     86 
     87    -- All 3 buffers are visible.
     88    assert_visible(1, true)
     89    assert_visible(2, true)
     90    assert_visible(3, true)
     91 
     92    -- Trigger WinClosed, which also deletes buffer/window 2.
     93    command('bdelete 1')
     94 
     95    -- Buffers 1 and 2 were closed but WinClosed was triggered only once.
     96    eq(1, eval('g:triggered'))
     97    assert_visible(1, false)
     98    assert_visible(2, false)
     99    assert_visible(3, true)
    100  end)
    101 
    102  it('WinClosed from a different tabpage', function()
    103    command('let g:evs = []')
    104    command('edit tesfile1')
    105    command('autocmd WinClosed <buffer> :call add(g:evs, ["WinClosed", expand("<abuf>")])')
    106    local buf1 = eval("bufnr('%')")
    107    command('new')
    108    local buf2 = eval("bufnr('%')")
    109    command(
    110      'autocmd WinClosed <buffer> :call add(g:evs, ["WinClosed", expand("<abuf>")])'
    111        -- Attempt recursion.
    112        .. ' | bdelete '
    113        .. buf2
    114    )
    115    command('tabedit testfile2')
    116    command('tabedit testfile3')
    117    command('bdelete ' .. buf2)
    118    -- Non-recursive: only triggered once.
    119    eq({
    120      { 'WinClosed', '2' },
    121    }, eval('g:evs'))
    122    command('bdelete ' .. buf1)
    123    eq({
    124      { 'WinClosed', '2' },
    125      { 'WinClosed', '1' },
    126    }, eval('g:evs'))
    127  end)
    128 
    129  it('WinClosed from root directory', function()
    130    command('cd /')
    131    command('let g:evs = []')
    132    command('autocmd WinClosed * :call add(g:evs, ["WinClosed", expand("<afile>")])')
    133    command('new')
    134    command('close')
    135    eq({
    136      { 'WinClosed', '1001' },
    137    }, eval('g:evs'))
    138  end)
    139 
    140  it('v:vim_did_enter is 1 after VimEnter', function()
    141    eq(1, eval('v:vim_did_enter'))
    142  end)
    143 
    144  describe('BufLeave autocommand', function()
    145    it('can wipe out the buffer created by :edit which triggered autocmd', function()
    146      api.nvim_set_option_value('hidden', true, {})
    147      api.nvim_buf_set_lines(0, 0, 1, false, {
    148        'start of test file xx',
    149        'end of test file xx',
    150      })
    151 
    152      command('autocmd BufLeave * bwipeout yy')
    153      eq(
    154        'Vim(edit):E143: Autocommands unexpectedly deleted new buffer yy',
    155        pcall_err(command, 'edit yy')
    156      )
    157 
    158      expect([[
    159        start of test file xx
    160        end of test file xx]])
    161    end)
    162  end)
    163 
    164  it('++once', function() -- :help autocmd-once
    165    --
    166    -- ":autocmd … ++once" executes its handler once, then removes the handler.
    167    --
    168    local expected = {
    169      'Many1',
    170      'Once1',
    171      'Once2',
    172      'Many2',
    173      'Once3',
    174      'Many1',
    175      'Many2',
    176      'Many1',
    177      'Many2',
    178    }
    179    command('let g:foo = []')
    180    command('autocmd TabNew * :call add(g:foo, "Many1")')
    181    command('autocmd TabNew * ++once :call add(g:foo, "Once1")')
    182    command('autocmd TabNew * ++once :call add(g:foo, "Once2")')
    183    command('autocmd TabNew * :call add(g:foo, "Many2")')
    184    command('autocmd TabNew * ++once :call add(g:foo, "Once3")')
    185    eq(
    186      dedent([[
    187 
    188       --- Autocommands ---
    189       TabNew
    190           *         :call add(g:foo, "Many1")
    191                     :call add(g:foo, "Once1")
    192                     :call add(g:foo, "Once2")
    193                     :call add(g:foo, "Many2")
    194                     :call add(g:foo, "Once3")]]),
    195      fn.execute('autocmd Tabnew')
    196    )
    197    command('tabnew')
    198    command('tabnew')
    199    command('tabnew')
    200    eq(expected, eval('g:foo'))
    201    eq(
    202      dedent([[
    203 
    204       --- Autocommands ---
    205       TabNew
    206           *         :call add(g:foo, "Many1")
    207                     :call add(g:foo, "Many2")]]),
    208      fn.execute('autocmd Tabnew')
    209    )
    210 
    211    --
    212    -- ":autocmd … ++once" handlers can be deleted.
    213    --
    214    expected = {}
    215    command('let g:foo = []')
    216    command('autocmd TabNew * ++once :call add(g:foo, "Once1")')
    217    command('autocmd! TabNew')
    218    command('tabnew')
    219    eq(expected, eval('g:foo'))
    220 
    221    --
    222    -- ":autocmd … <buffer> ++once ++nested"
    223    --
    224    expected = {
    225      'OptionSet-Once',
    226      'CursorMoved-Once',
    227    }
    228    command('let g:foo = []')
    229    command('autocmd OptionSet binary ++nested ++once :call add(g:foo, "OptionSet-Once")')
    230    command(
    231      'autocmd CursorMoved <buffer> ++once ++nested setlocal binary|:call add(g:foo, "CursorMoved-Once")'
    232    )
    233    command("put ='foo bar baz'")
    234    feed('0llhlh')
    235    eq(expected, eval('g:foo'))
    236 
    237    --
    238    -- :autocmd should not show empty section after ++once handlers expire.
    239    --
    240    expected = {
    241      'Once1',
    242      'Once2',
    243    }
    244    command('let g:foo = []')
    245    command('autocmd! TabNew') -- Clear all TabNew handlers.
    246    command('autocmd TabNew * ++once :call add(g:foo, "Once1")')
    247    command('autocmd TabNew * ++once :call add(g:foo, "Once2")')
    248    command('tabnew')
    249    eq(expected, eval('g:foo'))
    250    eq(
    251      dedent([[
    252 
    253       --- Autocommands ---]]),
    254      fn.execute('autocmd Tabnew')
    255    )
    256 
    257    --
    258    -- :autocmd does not recursively call ++once Lua handlers.
    259    --
    260    exec_lua [[vim.g.count = 0]]
    261    eq(0, eval('g:count'))
    262    exec_lua [[
    263      vim.api.nvim_create_autocmd('User', {
    264        once = true,
    265        pattern = nil,
    266        callback = function()
    267          vim.g.count = vim.g.count + 1
    268          vim.api.nvim_exec_autocmds('User', { pattern = nil })
    269        end,
    270      })
    271      vim.api.nvim_exec_autocmds('User', { pattern = nil })
    272    ]]
    273    eq(1, eval('g:count'))
    274  end)
    275 
    276  it('internal `aucmd_win` window', function()
    277    -- Nvim uses a special internal window `aucmd_win` to execute certain
    278    -- actions for an invisible buffer (:help E813).
    279    -- Check redrawing and API accesses to this window.
    280 
    281    local screen = Screen.new(50, 10)
    282 
    283    source([[
    284      function! Doit()
    285        let g:winid = nvim_get_current_win()
    286        redraw!
    287        echo getchar()
    288        " API functions work when aucmd_win is in scope
    289        let g:had_value = has_key(w:, "testvar")
    290        call nvim_win_set_var(g:winid, "testvar", 7)
    291        let g:test = w:testvar
    292      endfunction
    293      set hidden
    294      " add dummy text to not discard the buffer
    295      call setline(1,"bb")
    296      autocmd User <buffer> call Doit()
    297    ]])
    298    screen:expect([[
    299      ^bb                                                |
    300      {1:~                                                 }|*8
    301                                                        |
    302    ]])
    303 
    304    feed(':enew | doautoall User<cr>')
    305    screen:expect([[
    306      {4:bb                                                }|
    307      {11:~                                                 }|*4
    308      {1:~                                                 }|*4
    309      ^:enew | doautoall User                            |
    310    ]])
    311 
    312    feed('<cr>')
    313    screen:expect([[
    314      ^                                                  |
    315      {1:~                                                 }|*8
    316      13                                                |
    317    ]])
    318    eq(7, eval('g:test'))
    319 
    320    -- API calls are blocked when aucmd_win is not in scope
    321    eq(
    322      'Vim(call):E5555: API call: Invalid window id: 1001',
    323      pcall_err(command, 'call nvim_set_current_win(g:winid)')
    324    )
    325 
    326    -- second time aucmd_win is needed, a different code path is invoked
    327    -- to reuse the same window, so check again
    328    command('let g:test = v:null')
    329    command('let g:had_value = v:null')
    330    feed(':doautoall User<cr>')
    331    screen:expect([[
    332      {4:bb                                                }|
    333      {11:~                                                 }|*4
    334      {1:~                                                 }|*4
    335      ^:doautoall User                                   |
    336    ]])
    337 
    338    feed('<cr>')
    339    screen:expect([[
    340      ^                                                  |
    341      {1:~                                                 }|*8
    342      13                                                |
    343    ]])
    344    -- win vars in aucmd_win should have been reset
    345    eq(0, eval('g:had_value'))
    346    eq(7, eval('g:test'))
    347 
    348    eq(
    349      'Vim(call):E5555: API call: Invalid window id: 1001',
    350      pcall_err(command, 'call nvim_set_current_win(g:winid)')
    351    )
    352  end)
    353 
    354  it('`aucmd_win` cannot be changed into a normal window #13699', function()
    355    local screen = Screen.new(50, 10)
    356 
    357    -- Create specific layout and ensure it's left unchanged.
    358    -- Use vim._with on a hidden buffer so aucmd_win is used.
    359    exec_lua [[
    360      vim.cmd "wincmd s | wincmd _"
    361      _G.buf = vim.api.nvim_create_buf(true, true)
    362      vim._with({buf = _G.buf}, function() vim.cmd "wincmd J" end)
    363    ]]
    364    screen:expect [[
    365      ^                                                  |
    366      {1:~                                                 }|*5
    367      {3:[No Name]                                         }|
    368                                                        |
    369      {2:[No Name]                                         }|
    370                                                        |
    371    ]]
    372    -- This used to crash after making aucmd_win a normal window via the above.
    373    exec_lua [[
    374      vim.cmd "tabnew | tabclose # | wincmd s | wincmd _"
    375      vim._with({buf = _G.buf}, function() vim.cmd "wincmd K" end)
    376    ]]
    377    assert_alive()
    378    screen:expect_unchanged()
    379 
    380    -- Also check with win_splitmove().
    381    exec_lua [[
    382      vim._with({buf = _G.buf}, function()
    383        vim.fn.win_splitmove(vim.fn.win_getid(), vim.fn.win_getid(1))
    384      end)
    385    ]]
    386    screen:expect_unchanged()
    387 
    388    -- Also check with nvim_win_set_config().
    389    matches(
    390      '^Failed to move window %d+ into split$',
    391      pcall_err(
    392        exec_lua,
    393        [[
    394          vim._with({buf = _G.buf}, function()
    395            vim.api.nvim_win_set_config(0, {
    396              vertical = true,
    397              win = vim.fn.win_getid(1)
    398            })
    399          end)
    400        ]]
    401      )
    402    )
    403    screen:expect_unchanged()
    404 
    405    -- Ensure splitting still works from inside the aucmd_win.
    406    exec_lua [[vim._with({buf = _G.buf}, function() vim.cmd "split" end)]]
    407    screen:expect [[
    408      ^                                                  |
    409      {1:~                                                 }|
    410      {3:[No Name]                                         }|
    411                                                        |
    412      {1:~                                                 }|
    413      {2:[Scratch]                                         }|
    414                                                        |
    415      {1:~                                                 }|
    416      {2:[No Name]                                         }|
    417                                                        |
    418    ]]
    419 
    420    -- After all of our messing around, aucmd_win should still be floating.
    421    -- Use :only to ensure _G.buf is hidden again (so the aucmd_win is used).
    422    eq(
    423      'editor',
    424      exec_lua [[
    425        vim.cmd "only"
    426        vim._with({buf = _G.buf}, function()
    427          _G.config = vim.api.nvim_win_get_config(0)
    428        end)
    429        return _G.config.relative
    430      ]]
    431    )
    432  end)
    433 
    434  it('cannot close `aucmd_win` in non-current tabpage', function()
    435    exec([[
    436      file Xa
    437      tabnew Xb
    438      call setline(1, 'foo')
    439      tabfirst
    440      autocmd BufWriteCmd Xb tablast | bwipe! Xa
    441    ]])
    442    eq(
    443      'BufWriteCmd Autocommands for "Xb": Vim(bwipeout):E813: Cannot close autocmd window',
    444      pcall_err(command, 'wall')
    445    )
    446    -- Sanity check: :bwipe failing to close all windows into Xa should keep it loaded.
    447    -- (So there's no risk of it being left unloaded inside a window)
    448    eq(1, eval('bufloaded("Xa")'))
    449  end)
    450 
    451  describe('closing last non-floating window in tab from `aucmd_win`', function()
    452    before_each(function()
    453      command('edit Xa.txt')
    454      command('tabnew Xb.txt')
    455      command('autocmd BufAdd Xa.txt 1close')
    456    end)
    457 
    458    it('gives E814 when there are no other floating windows', function()
    459      eq(
    460        'BufAdd Autocommands for "Xa.txt": Vim(close):E814: Cannot close window, only autocmd window would remain',
    461        pcall_err(command, 'doautoall BufAdd')
    462      )
    463    end)
    464 
    465    it('gives E814 when there are other floating windows', function()
    466      api.nvim_open_win(
    467        0,
    468        true,
    469        { width = 10, height = 10, relative = 'editor', row = 10, col = 10 }
    470      )
    471      eq(
    472        'BufAdd Autocommands for "Xa.txt": Vim(close):E814: Cannot close window, only autocmd window would remain',
    473        pcall_err(command, 'doautoall BufAdd')
    474      )
    475    end)
    476  end)
    477 
    478  it('closing `aucmd_win` using API gives E813', function()
    479    exec_lua([[
    480      vim.cmd('tabnew')
    481      _G.buf = vim.api.nvim_create_buf(true, true)
    482    ]])
    483    matches(
    484      'Vim:E813: Cannot close autocmd window$',
    485      pcall_err(
    486        exec_lua,
    487        [[
    488          vim._with({buf = _G.buf}, function()
    489            local win = vim.api.nvim_get_current_win()
    490            vim.api.nvim_win_close(win, true)
    491          end)
    492        ]]
    493      )
    494    )
    495    matches(
    496      'Vim:E813: Cannot close autocmd window$',
    497      pcall_err(
    498        exec_lua,
    499        [[
    500          vim._with({buf = _G.buf}, function()
    501            local win = vim.api.nvim_get_current_win()
    502            vim.cmd('tabnext')
    503            vim.api.nvim_win_close(win, true)
    504          end)
    505        ]]
    506      )
    507    )
    508    matches(
    509      'Vim:E813: Cannot close autocmd window$',
    510      pcall_err(
    511        exec_lua,
    512        [[
    513          vim._with({buf = _G.buf}, function()
    514            local win = vim.api.nvim_get_current_win()
    515            vim.api.nvim_win_hide(win)
    516          end)
    517        ]]
    518      )
    519    )
    520    matches(
    521      'Vim:E813: Cannot close autocmd window$',
    522      pcall_err(
    523        exec_lua,
    524        [[
    525          vim._with({buf = _G.buf}, function()
    526            local win = vim.api.nvim_get_current_win()
    527            vim.cmd('tabnext')
    528            vim.api.nvim_win_hide(win)
    529          end)
    530        ]]
    531      )
    532    )
    533  end)
    534 
    535  it(':doautocmd does not warn "No matching autocommands" #10689', function()
    536    local screen = Screen.new(32, 3)
    537 
    538    feed(':doautocmd User Foo<cr>')
    539    screen:expect {
    540      grid = [[
    541      ^                                |
    542      {1:~                               }|
    543      :doautocmd User Foo             |
    544    ]],
    545    }
    546    feed(':autocmd! SessionLoadPost<cr>')
    547    feed(':doautocmd SessionLoadPost<cr>')
    548    screen:expect {
    549      grid = [[
    550      ^                                |
    551      {1:~                               }|
    552      :doautocmd SessionLoadPost      |
    553    ]],
    554    }
    555  end)
    556 
    557  describe('v:event is readonly #18063', function()
    558    it('during ChanOpen event', function()
    559      command('autocmd ChanOpen * let v:event.info.id = 0')
    560      fn.jobstart({ 'cat' })
    561      retry(nil, nil, function()
    562        eq('E46: Cannot change read-only variable "v:event.info"', api.nvim_get_vvar('errmsg'))
    563      end)
    564    end)
    565 
    566    it('during ChanOpen event', function()
    567      command('autocmd ChanInfo * let v:event.info.id = 0')
    568      api.nvim_set_client_info('foo', {}, 'remote', {}, {})
    569      retry(nil, nil, function()
    570        eq('E46: Cannot change read-only variable "v:event.info"', api.nvim_get_vvar('errmsg'))
    571      end)
    572    end)
    573 
    574    it('during RecordingLeave event', function()
    575      command([[autocmd RecordingLeave * let v:event.regname = '']])
    576      eq(
    577        'RecordingLeave Autocommands for "*": Vim(let):E46: Cannot change read-only variable "v:event.regname"',
    578        pcall_err(command, 'normal! qqq')
    579      )
    580    end)
    581 
    582    it('during TermClose event', function()
    583      command('autocmd TermClose * let v:event.status = 0')
    584      command('terminal')
    585      eq(
    586        'TermClose Autocommands for "*": Vim(let):E46: Cannot change read-only variable "v:event.status"',
    587        pcall_err(command, 'bdelete!')
    588      )
    589    end)
    590  end)
    591 
    592  describe('old_tests', function()
    593    it('vimscript: WinNew ++once', function()
    594      source [[
    595        " Without ++once WinNew triggers twice
    596        let g:did_split = 0
    597        augroup Testing
    598          au!
    599          au WinNew * let g:did_split += 1
    600        augroup END
    601        split
    602        split
    603        call assert_equal(2, g:did_split)
    604        call assert_true(exists('#WinNew'))
    605        close
    606        close
    607 
    608        " With ++once WinNew triggers once
    609        let g:did_split = 0
    610        augroup Testing
    611          au!
    612          au WinNew * ++once let g:did_split += 1
    613        augroup END
    614        split
    615        split
    616        call assert_equal(1, g:did_split)
    617        call assert_false(exists('#WinNew'))
    618        close
    619        close
    620 
    621        call assert_fails('au WinNew * ++once ++once echo bad', 'E983:')
    622      ]]
    623 
    624      api.nvim_set_var('did_split', 0)
    625 
    626      source [[
    627        augroup Testing
    628          au!
    629          au WinNew * let g:did_split += 1
    630        augroup END
    631 
    632        split
    633        split
    634      ]]
    635 
    636      eq(2, api.nvim_get_var('did_split'))
    637      eq(1, fn.exists('#WinNew'))
    638 
    639      -- Now with once
    640      api.nvim_set_var('did_split', 0)
    641 
    642      source [[
    643        augroup Testing
    644          au!
    645          au WinNew * ++once let g:did_split += 1
    646        augroup END
    647 
    648        split
    649        split
    650      ]]
    651 
    652      eq(1, api.nvim_get_var('did_split'))
    653      eq(0, fn.exists('#WinNew'))
    654 
    655      -- call assert_fails('au WinNew * ++once ++once echo bad', 'E983:')
    656      local ok, msg = pcall(
    657        source,
    658        [[
    659        au WinNew * ++once ++once echo bad
    660      ]]
    661      )
    662 
    663      eq(false, ok)
    664      eq(true, not not string.find(msg, 'E983:'))
    665    end)
    666 
    667    it('should have autocmds in filetypedetect group', function()
    668      source [[filetype on]]
    669      neq({}, api.nvim_get_autocmds { group = 'filetypedetect' })
    670    end)
    671 
    672    it('should allow comma-separated patterns', function()
    673      source [[
    674        augroup TestingPatterns
    675          au!
    676          autocmd BufReadCmd *.shada,*.shada.tmp.[a-z] echo 'hello'
    677          autocmd BufReadCmd *.shada,*.shada.tmp.[a-z] echo 'hello'
    678        augroup END
    679      ]]
    680 
    681      eq(4, #api.nvim_get_autocmds { event = 'BufReadCmd', group = 'TestingPatterns' })
    682    end)
    683  end)
    684 
    685  it('no use-after-free when adding autocommands from a callback', function()
    686    exec_lua [[
    687      vim.cmd "autocmd! TabNew"
    688      vim.g.count = 0
    689      vim.api.nvim_create_autocmd('TabNew', {
    690        callback = function()
    691          vim.g.count = vim.g.count + 1
    692          for _ = 1, 100 do
    693            vim.cmd "autocmd TabNew * let g:count += 1"
    694          end
    695          return true
    696        end,
    697      })
    698      vim.cmd "tabnew"
    699    ]]
    700    eq(1, eval('g:count')) -- Added autocommands should not be executed
    701  end)
    702 
    703  it('no crash when clearing a group inside a callback #23355', function()
    704    exec_lua [[
    705      vim.cmd "autocmd! TabNew"
    706      local group = vim.api.nvim_create_augroup('Test', {})
    707      local id
    708      id = vim.api.nvim_create_autocmd('TabNew', {
    709        group = group,
    710        callback = function()
    711          vim.api.nvim_del_autocmd(id)
    712          vim.api.nvim_create_augroup('Test', { clear = true })
    713        end,
    714      })
    715      vim.cmd "tabnew"
    716    ]]
    717  end)
    718 
    719  it('no use-after-free when wiping buffer in Syntax autocommand', function()
    720    exec([[
    721      new
    722      autocmd Syntax * ++once bwipe!
    723      setlocal syntax=vim
    724    ]])
    725    assert_alive()
    726  end)
    727 
    728  it('no use-after-free from win_enter autocommands in win_move_after', function()
    729    exec [[
    730      split foo
    731      split bar
    732      lcd ..
    733      wincmd b
    734    ]]
    735    eq(fn.winnr('$'), fn.winnr())
    736    -- Using DirChanged as Enter/Leave autocmds are blocked by :ball here.
    737    exec [[
    738      autocmd DirChanged * ++once split flarb | only!
    739      ball
    740    ]]
    741    eq('flarb', fn.bufname())
    742  end)
    743 
    744  it('does not ignore comma-separated patterns after a buffer-local pattern', function()
    745    exec [[
    746      edit baz  " reuses buffer 1
    747      edit bazinga
    748      edit bar
    749      edit boop
    750      edit foo
    751      edit floob
    752 
    753      let g:events1 = []
    754      autocmd BufEnter <buffer>,<buffer=1>,boop,bar let g:events1 += [expand('<afile>')]
    755      let g:events2 = []
    756      augroup flobby
    757        autocmd BufEnter <buffer=2>,foo let g:events2 += [expand('<afile>')]
    758        autocmd BufEnter foo,<buffer=3> let g:events2 += ['flobby ' .. expand('<afile>')]
    759      augroup END
    760    ]]
    761    eq(
    762      dedent([=[
    763 
    764      --- Autocommands ---
    765      BufEnter
    766          <buffer=6>
    767                    let g:events1 += [expand('<afile>')]
    768          <buffer=1>
    769                    let g:events1 += [expand('<afile>')]
    770          boop      let g:events1 += [expand('<afile>')]
    771          bar       let g:events1 += [expand('<afile>')]
    772      flobby  BufEnter
    773          <buffer=2>
    774                    let g:events2 += [expand('<afile>')]
    775          foo       let g:events2 += [expand('<afile>')]
    776                    let g:events2 += ['flobby ' .. expand('<afile>')]
    777          <buffer=3>
    778                    let g:events2 += ['flobby ' .. expand('<afile>')]]=]),
    779      fn.execute('autocmd BufEnter')
    780    )
    781    command('bufdo "')
    782    eq({ 'baz', 'bar', 'boop', 'floob' }, eval('g:events1'))
    783    eq({ 'bazinga', 'flobby bar', 'foo', 'flobby foo' }, eval('g:events2'))
    784 
    785    -- Also make sure it doesn't repeat the group/event name or pattern for each printed event.
    786    -- Do however repeat the pattern if the user specified it multiple times to be printed.
    787    -- These conditions aim to make the output consistent with Vim.
    788    eq(
    789      dedent([=[
    790 
    791      --- Autocommands ---
    792      BufEnter
    793          <buffer=1>
    794                    let g:events1 += [expand('<afile>')]
    795          bar       let g:events1 += [expand('<afile>')]
    796          bar       let g:events1 += [expand('<afile>')]
    797      flobby  BufEnter
    798          foo       let g:events2 += [expand('<afile>')]
    799                    let g:events2 += ['flobby ' .. expand('<afile>')]
    800          foo       let g:events2 += [expand('<afile>')]
    801                    let g:events2 += ['flobby ' .. expand('<afile>')]
    802      BufEnter
    803          <buffer=6>
    804                    let g:events1 += [expand('<afile>')]
    805          <buffer=6>
    806                    let g:events1 += [expand('<afile>')]
    807          <buffer=6>
    808                    let g:events1 += [expand('<afile>')]]=]),
    809      fn.execute('autocmd BufEnter <buffer=1>,bar,bar,foo,foo,<buffer>,<buffer=6>,<buffer>')
    810    )
    811  end)
    812 
    813  it('parses empty comma-delimited patterns correctly', function()
    814    exec [[
    815      autocmd User , "
    816      autocmd User ,, "
    817      autocmd User ,,according,to,,all,known,,,laws,, "
    818    ]]
    819    api.nvim_create_autocmd('User', { pattern = ',,of,,,aviation,,,,there,,', command = '' })
    820    api.nvim_create_autocmd('User', {
    821      pattern = { ',,,,is,,no', ',,way,,,', 'a,,bee{,should be, able to,},fly' },
    822      command = '',
    823    })
    824    eq(
    825      {
    826        'according',
    827        'to',
    828        'all',
    829        'known',
    830        'laws',
    831        'of',
    832        'aviation',
    833        'there',
    834        'is',
    835        'no',
    836        'way',
    837        'a',
    838        'bee{,should be, able to,}',
    839        'fly',
    840      },
    841      exec_lua(function()
    842        return vim.tbl_map(function(v)
    843          return v.pattern
    844        end, vim.api.nvim_get_autocmds({ event = 'User' }))
    845      end)
    846    )
    847    eq(
    848      dedent([[
    849 
    850      --- Autocommands ---
    851      User
    852          there
    853          is
    854          a
    855          fly]]),
    856      fn.execute('autocmd User ,,,there,is,,a,fly,,')
    857    )
    858  end)
    859 end)