From 89e915b67a23a4210a620f81d51f5dc9eb4c4bef Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 18 Oct 2026 14:39:13 -0400 Subject: [PATCH] fix(bufwrite): 'nomodified' if buffer changed during :write #42458 Problem: If a (naughty) autocmd (e.g. a `Progress` handler) changes the buffer during a slow `:write`, buf_write() still unsets 'modified', even though the buffer has unwritten changes. `:bdelete` would silently lose the changes. Solution: Reset 'modified' only if `b:changedtick` did not change while writing. --- src/nvim/bufwrite.c | 9 ++++++--- test/functional/ui/messages_spec.lua | 7 +++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/nvim/bufwrite.c b/src/nvim/bufwrite.c index fe9ede3cd9..68808f903f 100644 --- a/src/nvim/bufwrite.c +++ b/src/nvim/bufwrite.c @@ -1120,6 +1120,8 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en int progress = -1; // Progress-message percent for a slow write. off_T progress_nchars = 0; // `nchars` at the last progress check. const uint64_t write_start = os_hrtime(); + // Safety: any changes hereafter (eg by Progress autocmds) are not written, so keep 'modified'. + const varnumber_T write_tick = buf_get_changedtick(buf); msg_scroll = false; // always overwrite the file message now char *buffer = verbose_try_malloc(WRITEBUFSIZE); @@ -1759,11 +1761,12 @@ restore_backup: : msg_with_id(IObuff, msg_id, "bufwrite", NULL, 0, true, true, false), 0); } - // When written everything correctly: reset 'modified'. Unless not - // writing to the original file and '+' is not in 'cpoptions'. + // When written everything correctly: reset 'modified'. Unless not writing to the original file + // and not 'cpoptions=+', or the buffer changed (by autocmds!?) while writing. if (reset_changed && whole && !append && !write_info.bw_conv_error - && (overwriting || vim_strchr(p_cpo, kCpoPlus) != NULL)) { + && (overwriting || vim_strchr(p_cpo, kCpoPlus) != NULL) + && buf_get_changedtick(buf) == write_tick) { unchanged(buf, true, false); const varnumber_T changedtick = buf_get_changedtick(buf); if (buf->b_last_changedtick + 1 == changedtick) { diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 64e70cffae..02d9e5a72a 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -3957,10 +3957,15 @@ describe('progress-message', function() return exec_lua(function(f, cmd) local out = {} vim.api.nvim_create_autocmd('Progress', { + group = vim.api.nvim_create_augroup('test_slow_write', {}), callback = function(ev) table.insert(out, ev.data.status) -- :bwipeout disallowed (E937) while writing. out.wiped = out.wiped or pcall(vim.cmd.bwipe, { bang = true }) + if #out == 1 then + -- Progress handler (naughty) edits the buffer DURING :write. + vim.api.nvim_buf_set_lines(0, -1, -1, true, { 'not written' }) + end end, }) vim.api.nvim_buf_set_lines(0, 0, -1, true, vim.fn['repeat']({ ('x'):rep(99) }, 2000)) @@ -3978,6 +3983,8 @@ describe('progress-message', function() end eq({ 'running', 'success', false, 'ok' }, slow_write('cat')) + -- Buffer changed while writing: still 'modified'. + eq(true, api.nvim_get_option_value('modified', {})) eq({ 'running', 'failed', false, 'E514' }, slow_write('head -c 100000')) -- Write error (EPIPE). end)