Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 0 additions & 23 deletions lua/diffview/scene/views/diff/diff_view.lua
Original file line number Diff line number Diff line change
Expand Up @@ -409,29 +409,6 @@ function DiffView:close(opts)
return true
end

---@private
---@param self DiffView
---@param file FileEntry
DiffView._set_file = async.void(function(self, file)
self.panel:render()
self.panel:redraw()
vim.cmd("redraw")

self.cur_layout:detach_files()
local cur_entry = self.cur_entry
self.emitter:emit("file_open_pre", file, cur_entry)
self.nulled = false

await(self:use_entry(file))

self.emitter:emit("file_open_post", file, cur_entry)

if not self.cur_entry.opened then
self.cur_entry.opened = true
DiffviewGlobal.emitter:emit("file_open_new", file)
end
end)

---Open the next file.
---@param highlight? boolean Bring the cursor to the file entry in the panel.
---@return FileEntry?
Expand Down
44 changes: 10 additions & 34 deletions lua/diffview/scene/views/file_history/file_history_view.lua
Original file line number Diff line number Diff line change
Expand Up @@ -157,40 +157,16 @@ function FileHistoryView:_destroy_pinned_b_files()
self._pinned_b_files = {}
end

---@private
---@param self FileHistoryView
---@param file FileEntry
FileHistoryView._set_file = async.void(function(self, file)
self.panel:render()
self.panel:redraw()
vim.cmd("redraw")

-- Use the swap variant so pinned layouts can keep the pinned (b) window
-- bound across the swap; tab-leave / view-close still call `detach_files`
-- and tear down everything. Passing the next entry lets pinned variants
-- compare the upcoming b-file against the current one and detach when
-- they differ (multi-file pinning crossing a row to a different path).
self.cur_layout:detach_files_for_swap(file)
local cur_entry = self.cur_entry
self.emitter:emit("file_open_pre", file, cur_entry)
self.nulled = false

await(self:use_entry(file))

-- NOTE: Do NOT set foldmethod=manual on these diff windows. The
-- combination of diff=true and foldmethod=manual triggers a Neovim bug
-- where the screen redraw enters an infinite loop for certain buffer
-- pairs, permanently freezing the editor. Neovim's built-in
-- foldmethod=diff already folds unchanged regions in the diff.
-- See: sindrets/diffview.nvim#552

self.emitter:emit("file_open_post", file, cur_entry)

if not self.cur_entry.opened then
self.cur_entry.opened = true
DiffviewGlobal.emitter:emit("file_open_new", file)
end
end)
---@override
---Use the swap variant so pinned layouts can keep the pinned (b) window
---bound across the swap; tab-leave / view-close still call `detach_files`
---and tear down everything. Passing the next entry lets pinned variants
---compare the upcoming b-file against the current one and detach when
---they differ (multi-file pinning crossing a row to a different path).
---@param next_file FileEntry
function FileHistoryView:_detach_files_for_next(next_file)
self.cur_layout:detach_files_for_swap(next_file)
end

function FileHistoryView:next_item()
self:ensure_layout()
Expand Down
74 changes: 74 additions & 0 deletions lua/diffview/scene/views/standard/standard_view.lua
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ local M = {}
---@field cur_layout Layout
---@field cur_entry FileEntry
---@field layouts table<Layout, Layout>
---@field package _set_file_in_flight Future? # Active `_set_file` worker; queued callers await this so `await(set_file)` returns only after the latest pending file is opened.
---@field package _set_file_pending FileEntry? # Newest file queued while `_set_file_in_flight` is set; the worker picks it up before terminating.
local StandardView = oop.create_class("StandardView", View.__get())

---StandardView constructor
Expand Down Expand Up @@ -232,6 +234,78 @@ StandardView.use_entry = async.void(function(self, entry)
end
end)

---Set the active file. Coalesces rapid navigation: if a previous
---`_set_file` is still running (e.g., user mashing `<Tab>` faster than
---the async HEAD~ git fetch can complete), only the newest pending file
---is kept; the in-flight worker picks it up after finishing its current
---target. Without this guard, two concurrent `_set_file` coroutines
---share the same windows: the second's `Layout.use_entry` overwrites
---`win.file`, and the first's `open_file` then runs `set_win_buf`
---against the second file's bufnr while its content is still loading,
---placing an empty buffer in the window so `]c` in
---`jump_to_first_change` finds no changes and leaves the cursor at line
---1.
---
---This is a plain (non-async) function so non-awaited callers (rapid
---`next_file`/`prev_file` taps) don't spawn a wrapper task per call;
---they just update the pending slot and reuse the existing worker
---Future. Awaited callers (e.g., `set_file` from conflict resolution)
---can still `await(view:_set_file(item))` and resume only once the view
---has actually switched to the latest pending file.
---@param file FileEntry
---@return Future
function StandardView:_set_file(file)
self._set_file_pending = file
if self._set_file_in_flight and not self._set_file_in_flight:is_done() then
return self._set_file_in_flight
end
self._set_file_in_flight = self:_drain_set_file_pending()
return self._set_file_in_flight
end

---@param self StandardView
StandardView._drain_set_file_pending = async.void(function(self)
while self._set_file_pending do
local target = self._set_file_pending --[[@as FileEntry]]
self._set_file_pending = nil

self.panel:render()
self.panel:redraw()
vim.cmd("redraw")

self:_detach_files_for_next(target)
local cur_entry = self.cur_entry
self.emitter:emit("file_open_pre", target, cur_entry)
self.nulled = false

await(self:use_entry(target))

-- NOTE: Do NOT set foldmethod=manual on these diff windows. The
-- combination of diff=true and foldmethod=manual triggers a Neovim bug
-- where the screen redraw enters an infinite loop for certain buffer
-- pairs, permanently freezing the editor. Neovim's built-in
-- foldmethod=diff already folds unchanged regions in the diff.
-- See: sindrets/diffview.nvim#552

self.emitter:emit("file_open_post", target, cur_entry)

if not self.cur_entry.opened then
self.cur_entry.opened = true
DiffviewGlobal.emitter:emit("file_open_new", target)
end
end
self._set_file_in_flight = nil
end)

---Detach files from the current layout before switching to `next_file`.
---Subclasses override when the swap semantics differ (e.g., pinned
---layouts in `FileHistoryView` keep specific windows bound across the
---swap).
---@param next_file FileEntry
function StandardView:_detach_files_for_next(next_file) ---@diagnostic disable-line: unused-local
self.cur_layout:detach_files()
end

M.StandardView = StandardView

return M
4 changes: 4 additions & 0 deletions lua/diffview/tests/functional/diff_view_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -667,4 +667,8 @@ describe("diffview.scene.views.diff.DiffView", function()
config.setup(original_config)
end)
end)

-- `_set_file` rapid-navigation coalescing is exercised against
-- `StandardView` (the shared owner of the worker) in
-- `standard_view_spec.lua`; DiffView inherits the behavior unchanged.
end)
Loading
Loading