Skip to content

Commit 12e64cf

Browse files
committed
feat(ui/output_window): preserve manually opened folds when reapplying fold ranges
1 parent 3b085e6 commit 12e64cf

2 files changed

Lines changed: 64 additions & 9 deletions

File tree

lua/opencode/ui/output_window.lua

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ M._prev_line_count_by_win = {}
1313
local _update_depth = 0
1414
local _update_buf = nil
1515

16+
local function is_win_showing_buf(win, buf)
17+
return win and vim.api.nvim_win_is_valid(win) and vim.api.nvim_win_get_buf(win) == buf
18+
end
19+
1620
---Begin a batch of buffer writes — toggle modifiable once for the whole batch.
1721
---Returns true if the batch was opened (buffer is valid). Must be paired with end_update().
1822
---@return boolean
@@ -320,29 +324,60 @@ end
320324
_G.opencode_fold_expr = M.fold_expr
321325
_G.opencode_fold_text = M.fold_text
322326

327+
function M.snapshot_open_folds(win, prev_folds)
328+
local ok, result = pcall(vim.api.nvim_win_call, win, function()
329+
local open = {}
330+
for _, range in ipairs(prev_folds) do
331+
if vim.fn.foldclosed(range.from) == -1 then
332+
open[#open + 1] = range.from
333+
end
334+
end
335+
return open
336+
end)
337+
return ok and result or {}
338+
end
339+
340+
function M.restore_open_folds(win, open_fold_starts)
341+
pcall(vim.api.nvim_win_call, win, function()
342+
local view = vim.fn.winsaveview()
343+
vim.cmd('silent! normal! zX')
344+
for _, lnum in ipairs(open_fold_starts) do
345+
if vim.fn.foldclosed(lnum) ~= -1 then
346+
vim.fn.cursor(lnum, 1)
347+
vim.cmd('silent! normal! zo')
348+
end
349+
end
350+
vim.fn.winrestview(view)
351+
end)
352+
end
353+
323354
---Set the folds for the output buffer
324355
---@param fold_ranges table<{from: number, to: number}>
325356
function M.set_folds(fold_ranges)
326357
local windows = state.windows
327-
if not windows or not windows.output_buf or not vim.api.nvim_buf_is_valid(windows.output_buf) then
358+
local buf = windows.output_buf
359+
if not M.mounted() then
328360
return
329361
end
330362

331363
local folds = fold_ranges or {}
332-
local buf = windows.output_buf
333-
local win = windows.output_win
334-
335364
local ok, prev_folds = pcall(vim.api.nvim_buf_get_var, buf, 'opencode_folds')
336-
if ok and vim.deep_equal(prev_folds, folds) then
365+
if ok and #folds == #prev_folds and vim.deep_equal(prev_folds, folds) then
337366
return
338367
end
339368

369+
local win = windows.output_win
370+
local win_owns_buf = is_win_showing_buf(win, buf)
371+
372+
local open_fold_starts = {}
373+
if ok and win_owns_buf then
374+
open_fold_starts = M.snapshot_open_folds(win, prev_folds)
375+
end
376+
340377
vim.api.nvim_buf_set_var(buf, 'opencode_folds', folds)
341378

342-
if win and vim.api.nvim_win_is_valid(win) and vim.api.nvim_win_get_buf(win) == buf then
343-
pcall(vim.api.nvim_win_call, win, function()
344-
vim.cmd('silent! normal! zX')
345-
end)
379+
if win_owns_buf then
380+
M.restore_open_folds(win, open_fold_starts)
346381
end
347382
end
348383

tests/unit/output_window_spec.lua

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,26 @@ describe('output_window.setup', function()
164164

165165
assert.equals(1, foldclosed)
166166
end)
167+
168+
it('preserves manually opened folds when fold ranges are reapplied', function()
169+
output_window.setup({ output_buf = buf, output_win = win })
170+
output_window.set_lines({ 'a', 'b', 'c', 'd', 'e' })
171+
172+
output_window.set_folds({ { from = 1, to = 3 } })
173+
174+
vim.api.nvim_win_call(win, function()
175+
vim.fn.cursor(1, 1)
176+
vim.cmd('silent! normal! zo')
177+
end)
178+
179+
output_window.set_folds({ { from = 1, to = 4 } })
180+
181+
local foldclosed = vim.api.nvim_win_call(win, function()
182+
return vim.fn.foldclosed(1)
183+
end)
184+
185+
assert.equals(-1, foldclosed)
186+
end)
167187
end)
168188

169189
describe('output_window extmarks', function()

0 commit comments

Comments
 (0)