From be8d997fa9887e52c18ac00a865dd126a93e31a2 Mon Sep 17 00:00:00 2001 From: David Yonge-Mallo Date: Sun, 6 Sep 2026 21:09:28 +0200 Subject: [PATCH] fix(entry): guard LOCAL buffers from force-destroy in `FileEntry:destroy` --- lua/diffview/scene/file_entry.lua | 13 +++- .../tests/functional/file_entry_spec.lua | 59 ++++++++++++++++++- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/lua/diffview/scene/file_entry.lua b/lua/diffview/scene/file_entry.lua index 027f5a2a..c9e3df6a 100644 --- a/lua/diffview/scene/file_entry.lua +++ b/lua/diffview/scene/file_entry.lua @@ -98,14 +98,23 @@ function FileEntry:init(opt) self._extra_owned = opt._extra_owned or {} end +---Destroy owned Files. `force` still drops COMMIT/STAGE/CUSTOM buffers +---unconditionally, but LOCAL buffers stay guarded by `is_buf_in_use`: they +---represent the user's real file buffer and may be visible in windows +---outside the diffview (e.g., the tab the user opened from). Force-deleting +---one would collapse that tab and lose the cursor position there. ---@param force? boolean function FileEntry:destroy(force) + local function force_for(f) + return force and f.rev and f.rev.type ~= RevType.LOCAL + end + for _, f in ipairs(self.layout:owned_files()) do - f:destroy(force) + f:destroy(force_for(f)) end for _, f in ipairs(self._extra_owned) do - f:destroy(force) + f:destroy(force_for(f)) end self._extra_owned = {} diff --git a/lua/diffview/tests/functional/file_entry_spec.lua b/lua/diffview/tests/functional/file_entry_spec.lua index 6039ebf4..ef854843 100644 --- a/lua/diffview/tests/functional/file_entry_spec.lua +++ b/lua/diffview/tests/functional/file_entry_spec.lua @@ -425,17 +425,29 @@ describe("diffview.scene.file_entry", function() eq(0, #entry._extra_owned) end) - it("forwards force flag to contained files when destroyed", function() + it("forwards force to non-LOCAL files but guards LOCAL when destroyed", function() local seen = {} local layout_destroyed = false + -- LOCAL files stand in for the user's real working-tree buffer, which + -- may be visible in other tabs; `FileEntry:destroy` must never propagate + -- `force=true` to them. Non-LOCAL sides (STAGE/COMMIT) remain forced so + -- `refresh_files({ force = true })` still discards their virtual buffers. local files_list = { { + rev = { type = RevType.STAGE }, destroy = function(_, force) seen[#seen + 1] = force end, }, { + rev = { type = RevType.LOCAL }, + destroy = function(_, force) + seen[#seen + 1] = force + end, + }, + { + rev = { type = RevType.COMMIT }, destroy = function(_, force) seen[#seen + 1] = force end, @@ -466,7 +478,50 @@ describe("diffview.scene.file_entry", function() entry:destroy(true) - eq({ true, true }, seen) + eq({ true, false, true }, seen) eq(true, layout_destroyed) end) + + it("passes force=false through unchanged regardless of rev type", function() + local seen = {} + + local files_list = { + { + rev = { type = RevType.STAGE }, + destroy = function(_, force) + seen[#seen + 1] = force + end, + }, + { + rev = { type = RevType.LOCAL }, + destroy = function(_, force) + seen[#seen + 1] = force + end, + }, + } + local layout = { + files = function() + return files_list + end, + owned_files = function() + return files_list + end, + destroy = function() end, + } + + local entry = FileEntry({ + adapter = { ctx = { toplevel = "/tmp" } }, + path = "a.txt", + oldpath = nil, + revs = {}, + layout = layout, + status = "M", + stats = {}, + kind = "working", + }) + + entry:destroy(false) + + eq({ false, false }, seen) + end) end)