diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index f9170b0c93..fba19ba9f0 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -657,6 +657,10 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i buf->b_nwindows = nwindows; + // Disable buffer-updates for the current buffer. + // No need to check `unload_buf`: in that case the function returned above. + buf_updates_unload(buf, false); + buf_freeall(buf, ((del_buf ? BFA_DEL : 0) + (wipe_buf ? BFA_WIPE : 0) + (ignore_abort ? BFA_IGNORE_ABORT : 0))); @@ -679,10 +683,6 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i return false; } - // Disable buffer-updates for the current buffer. - // No need to check `unload_buf`: in that case the function returned above. - buf_updates_unload(buf, false); - if (win != NULL // Avoid bogus clang warning. && win_valid_any_tab(win) && win->w_buffer == buf) { diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index b9c1e9f897..eb3deadc10 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -1384,3 +1384,44 @@ describe('lua: nvim_buf_attach on_bytes', function() do_both(false) end) end) + +describe('nvim_buf_attach on_detach', function() + it('is invoked before unloading buffer', function() + exec_lua(function() + _G.logs = {} ---@type table + end) + local function setup(bufnr) + exec_lua(function() + _G.logs[bufnr] = {} + vim.api.nvim_create_autocmd({ 'BufUnload', 'BufWipeout' }, { + buffer = bufnr, + callback = function(ev) + table.insert(_G.logs[bufnr], ev.event) + end, + }) + vim.api.nvim_buf_attach(bufnr, false, { + on_detach = function() + table.insert(_G.logs[bufnr], 'on_detach') + end, + }) + end) + end + -- Test with two buffers because the :bw works differently for the last buffer. + -- Before #35355, the order was as follows: + -- * non-last buffers: BufUnload → BufWipeout → on_detach + -- * the last buffer (with text): BufUnload → on_detach → BufWipeout + local buf1 = api.nvim_get_current_buf() + local buf2 = api.nvim_create_buf(true, false) + api.nvim_open_win(buf2, false, { split = 'below' }) + api.nvim_buf_set_lines(buf1, 0, -1, true, { 'abc' }) + api.nvim_buf_set_lines(buf2, 0, -1, true, { 'abc' }) + setup(buf1) + setup(buf2) + api.nvim_buf_delete(buf1, { force = true }) + api.nvim_buf_delete(buf2, { force = true }) + local logs = exec_lua('return _G.logs') + local order = { 'on_detach', 'BufUnload', 'BufWipeout' } + eq(order, logs[buf1]) + eq(order, logs[buf2]) + end) +end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index c23734dc27..06bbc7d25c 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -621,6 +621,37 @@ describe('LSP', function() eq(true, result.detach_called) end) + it('should detach buffer on bufwipe 2', function() + exec_lua(create_server_definition) + local result = exec_lua(function() + local server = _G._create_server() + local bufnr1 = vim.api.nvim_create_buf(false, true) + local bufnr2 = vim.api.nvim_create_buf(false, true) + local detach_called1 = false + local detach_called2 = false + vim.api.nvim_create_autocmd('LspDetach', { + buffer = bufnr1, + callback = function() + detach_called1 = true + end, + }) + vim.api.nvim_create_autocmd('LspDetach', { + buffer = bufnr2, + callback = function() + detach_called2 = true + end, + }) + vim.api.nvim_set_current_buf(bufnr1) + vim.lsp.start({ name = 'detach-dummy', cmd = server.cmd }) + vim.api.nvim_set_current_buf(bufnr2) + vim.lsp.start({ name = 'detach-dummy', cmd = server.cmd }) + vim.api.nvim_buf_delete(bufnr1, { force = true }) + vim.api.nvim_buf_delete(bufnr2, { force = true }) + return detach_called1 and detach_called2 + end) + eq(true, result) + end) + it('should not re-attach buffer if it was deleted in on_init #28575', function() exec_lua(create_server_definition) exec_lua(function()