diff --git a/lua/diffview/scene/window.lua b/lua/diffview/scene/window.lua index 41af90ee..53b14efe 100644 --- a/lua/diffview/scene/window.lua +++ b/lua/diffview/scene/window.lua @@ -142,16 +142,24 @@ Window.open_file = async.void(function(self) ---@diagnostic disable: invisible assert(self.file) - if not (self:is_valid() and self.file.active) then + if not self:is_valid() then return end if not self.file:is_valid() then + -- Skip the load entirely if the file was already deactivated by the + -- time we got here -- the user has navigated past this target, so a + -- fresh `git show` would be wasted work. Once the load is in flight, + -- `create_buffer` raises `CANCELLED` on its own active checks, which + -- surfaces as `ok = false` below. + if not self.file.active then + return + end local ok = await(self:load_file()) await(async.scheduler()) -- Ensure validity after await - if not (self:is_valid() and self.file.active) then + if not self:is_valid() then return end @@ -161,6 +169,19 @@ Window.open_file = async.void(function(self) end end + -- The file is loaded; display it regardless of `self.file.active`. + -- `Layout.open_files` yields on `async.scheduler()` between its load + -- loop and its open loop, and a rapid navigation in that gap can + -- deactivate the file. Bailing here on `active=false` used to leave + -- the window holding the prior `open_null` placeholder while + -- `file.loaded=true` claimed the buffer was ready -- a stale state + -- that trips `jump_to_first_change` (silent `]c` on an empty buffer + -- in the default branch; `Cursor position outside buffer` in + -- `diff1_inline`, which looks up hunks on `file.bufnr` and writes the + -- cursor in `main.id`'s current buffer). The buffer is already + -- populated, so setting it in the window is cheap; the next worker + -- iteration replaces it with the user's actual target. + self.emitter:emit("pre_open") -- Disable context plugins BEFORE the buffer enters the window. diff --git a/lua/diffview/tests/functional/window_spec.lua b/lua/diffview/tests/functional/window_spec.lua index e838186d..c11a130a 100644 --- a/lua/diffview/tests/functional/window_spec.lua +++ b/lua/diffview/tests/functional/window_spec.lua @@ -303,6 +303,38 @@ describe("diffview.scene.window", function() vim.api.nvim_buf_delete(bufnr, { force = true }) end) ) + + -- Regression: `Layout.open_files` yields between its load loop and its + -- open loop. A rapid navigation in that gap deactivates the in-flight + -- file; bailing on `active=false` here used to leave the window holding + -- the `open_null` placeholder while `file.loaded=true` claimed the + -- buffer was ready, tripping `jump_to_first_change` (silent `]c` on an + -- empty buffer; "Cursor position outside buffer" in `diff1_inline`). + -- Once the buffer is populated, displaying it is cheap, so `open_file` + -- proceeds regardless of `active`. + it( + "displays an already-loaded buffer even when the file got deactivated", + helpers.async_test(function() + local adapter = mock_adapter() + local bufnr = vim.api.nvim_create_buf(false, true) + local win, file = make_window(adapter) + file.bufnr = bufnr + file.loaded = true + file.active = false -- deactivated post-load. + win.parent = stub_parent() + + async.await(win:open_file()) + + assert.equals(bufnr, vim.api.nvim_win_get_buf(win.id)) + + if vim.api.nvim_win_is_valid(test_winid) then + vim.api.nvim_win_close(test_winid, true) + end + test_winid = nil + Window.winopt_store[bufnr] = nil + vim.api.nvim_buf_delete(bufnr, { force = true }) + end) + ) end) it(