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
47 changes: 43 additions & 4 deletions lua/diffview/scene/window.lua
Original file line number Diff line number Diff line change
Expand Up @@ -216,13 +216,17 @@ Window.open_file = async.void(function(self)
return
end

-- On the content side of an added/deleted file `foldmethod=diff`
-- produces no folds; leave the buffer's own folding alone. See #299.
local solo_content_side = self:_paired_side_is_null()

-- Apply the configured foldlevel before `_save_winopts` so the saved
-- value covers the key we're about to override. Scope this to diff
-- buffers (where `diff` is not explicitly false), matching the
-- documented purpose of `view.foldlevel`; non-diff layouts like
-- `diff1_raw` opt out via `diff = false` and supply their own
-- foldlevel via the layout winopts.
if self.file.winopts and self.file.winopts.diff ~= false then
if self.file.winopts and self.file.winopts.diff ~= false and not solo_content_side then
self.file.winopts.foldlevel = conf.view.foldlevel
end

Expand All @@ -233,7 +237,7 @@ Window.open_file = async.void(function(self)
if self:is_nulled() then
self:apply_null_winopts()
else
self:apply_file_winopts()
self:apply_file_winopts({ skip_fold = solo_content_side })
end

local view = lib.get_current_view()
Expand Down Expand Up @@ -366,13 +370,48 @@ function Window:_restore_winopts()
end
end

function Window:apply_file_winopts()
-- Fold-related winopts skipped by `apply_file_winopts` when `skip_fold`
-- is set. See #299.
local FOLD_OPTS = { foldmethod = true, foldenable = true, foldlevel = true }

---@param opt? { skip_fold?: boolean }
function Window:apply_file_winopts(opt)
assert(self.file)
if self.file.winopts then
if not self.file.winopts then
return
end

if opt and opt.skip_fold then
local filtered = {}
for k, v in pairs(self.file.winopts) do
if not FOLD_OPTS[k] then
filtered[k] = v
end
end
utils.set_local(self.id, filtered)
else
utils.set_local(self.id, self.file.winopts)
end
end

---True when a sibling window in the parent layout is nulled: the
---content side of an added/deleted file.
---@return boolean
function Window:_paired_side_is_null()
local windows = self.parent and self.parent.windows
if not windows then
return false
end

for _, other in ipairs(windows) do
if other ~= self and other.file and other.file.nulled then
return true
end
end

return false
end

function Window:apply_null_winopts()
if File.NULL_FILE.winopts then
utils.set_local(self.id, File.NULL_FILE.winopts)
Expand Down
70 changes: 70 additions & 0 deletions lua/diffview/tests/functional/window_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,76 @@ describe("diffview.scene.window", function()
end)
)

-- Regression (#299): fold winopts must be skipped on the content
-- side of an added/deleted file, so the buffer's own folding
-- (ftplugin, treesitter, ufo, ...) survives.
it(
"does not override fold options when the paired window's file is nulled",
helpers.async_test(function()
config.setup({ view = { foldlevel = 0 } })

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

local sibling_file = { nulled = true }
local sibling_win = { file = sibling_file }
win.parent = vim.tbl_extend("force", stub_parent(), { windows = { win, sibling_win } })

-- Values distinct from what `winopts` would install, so survival
-- proves the override was skipped rather than coincidentally matching.
vim.wo[win.id].foldmethod = "marker"
vim.wo[win.id].foldlevel = 42
vim.wo[win.id].foldenable = false

async.await(win:open_file())

assert.equals("marker", vim.wo[win.id].foldmethod)
assert.equals(42, vim.wo[win.id].foldlevel)
assert.is_false(vim.wo[win.id].foldenable)

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)
)

it(
"still applies fold options when no sibling window is nulled",
helpers.async_test(function()
config.setup({ view = { foldlevel = 0 } })

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

local sibling_file = { nulled = false }
local sibling_win = { file = sibling_file }
win.parent = vim.tbl_extend("force", stub_parent(), { windows = { win, sibling_win } })

vim.wo[win.id].foldmethod = "marker"

async.await(win:open_file())

assert.equals("diff", vim.wo[win.id].foldmethod)
assert.equals(0, vim.wo[win.id].foldlevel)

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)
)

-- 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
Expand Down
Loading