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.
This commit is contained in:
Justin M. Keyes authored and GitHub committed 2026-10-18 14:39:13 -04:00
1 parent eb25a1192a
commit 89e915b67a
2 files changed
+13 -3

No files matched your search

+6 -3
View File
@@ -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) {
+7
View File
@@ -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)