@@ -11,6 +11,65 @@ M._last_visible_bottom_by_win = {}
1111M ._was_at_bottom_by_win = {}
1212M ._prev_line_count_by_win = {}
1313
14+ local function build_fold_state (folds )
15+ local fold_state = {
16+ ranges = {},
17+ starts = {},
18+ }
19+
20+ for _ , range in ipairs (folds or {}) do
21+ if range .from and range .to then
22+ fold_state .ranges [# fold_state .ranges + 1 ] = {
23+ from = range .from ,
24+ to = range .to ,
25+ }
26+ fold_state .starts [# fold_state .starts + 1 ] = range .from
27+ end
28+ end
29+
30+ table.sort (fold_state .ranges , function (a , b )
31+ return a .from < b .from
32+ end )
33+ table.sort (fold_state .starts )
34+
35+ return fold_state
36+ end
37+
38+ --- @param buf integer
39+ --- @return { ranges : table< {from : integer , to : integer } >, starts : integer[] }
40+ local function get_fold_state (buf )
41+ local ok , fold_state = pcall (vim .api .nvim_buf_get_var , buf , ' opencode_folds' )
42+ if not ok or type (fold_state ) ~= ' table' then
43+ return { ranges = {}, starts = {} }
44+ end
45+ if type (fold_state .ranges ) == ' table' and type (fold_state .starts ) == ' table' then
46+ return fold_state
47+ end
48+ return build_fold_state (fold_state )
49+ end
50+
51+ --- @param ranges table< {from : integer , to : integer } >
52+ --- @param line integer
53+ --- @return boolean
54+ local function line_in_fold (ranges , line )
55+ local lo = 1
56+ local hi = # ranges
57+
58+ while lo <= hi do
59+ local mid = math.floor ((lo + hi ) / 2 )
60+ local range = ranges [mid ]
61+ if line < range .from then
62+ hi = mid - 1
63+ elseif line > range .to then
64+ lo = mid + 1
65+ else
66+ return true
67+ end
68+ end
69+
70+ return false
71+ end
72+
1473local _update_depth = 0
1574local _update_buf = nil
1675
@@ -48,7 +107,7 @@ function M.create_buf()
48107 local filetype = config .ui .output .filetype or ' opencode_output'
49108 vim .api .nvim_set_option_value (' filetype' , filetype , { buf = output_buf })
50109
51- vim .api .nvim_buf_set_var (output_buf , ' opencode_folds' , {} )
110+ vim .api .nvim_buf_set_var (output_buf , ' opencode_folds' , build_fold_state ({}) )
52111
53112 local buffixwin = require (' opencode.ui.buf_fix_win' )
54113 buffixwin .fix_to_win (output_buf , function ()
@@ -270,19 +329,8 @@ function M.fold_expr()
270329 end
271330
272331 local line = vim .v .lnum
273- local ok , folds = pcall (function ()
274- return vim .api .nvim_buf_get_var (output_buf , ' opencode_folds' )
275- end )
276- if not ok or not folds then
277- return 0
278- end
279-
280- for _ , range in ipairs (folds ) do
281- if line >= range .from and line <= range .to then
282- return 1
283- end
284- end
285- return 0
332+ local fold_state = get_fold_state (output_buf )
333+ return line_in_fold (fold_state .ranges , line ) and 1 or 0
286334end
287335
288336--- Fold text for the output buffer
@@ -296,12 +344,7 @@ function M.fold_text()
296344 return vim .fn .foldtext ()
297345 end
298346
299- local ok , folds = pcall (function ()
300- return vim .api .nvim_buf_get_var (output_buf , ' opencode_folds' )
301- end )
302- if not ok or not folds then
303- return vim .fn .foldtext ()
304- end
347+ local folds = get_fold_state (output_buf ).ranges
305348
306349 local line_count = 0
307350 for _ , range in ipairs (folds ) do
@@ -328,10 +371,7 @@ function M.get_open_fold_starts(win, buf)
328371 return {}
329372 end
330373
331- local ok , prev_folds = pcall (vim .api .nvim_buf_get_var , buf , ' opencode_folds' )
332- if not ok or not prev_folds then
333- return {}
334- end
374+ local prev_folds = get_fold_state (buf ).ranges
335375
336376 local was_open = {}
337377 vim .api .nvim_win_call (win , function ()
@@ -356,12 +396,10 @@ function M.set_folds(fold_ranges)
356396
357397 local buf = windows .output_buf
358398 local win = windows .output_win
359- local folds = fold_ranges or {}
399+ local folds = build_fold_state (fold_ranges or {})
400+ local prev_folds = get_fold_state (buf )
360401
361- local ok , prev_folds = pcall (vim .api .nvim_buf_get_var , buf , ' opencode_folds' )
362- prev_folds = ok and prev_folds or {}
363-
364- if # folds == # prev_folds and vim .deep_equal (prev_folds , folds ) then
402+ if vim .deep_equal (prev_folds .ranges , folds .ranges ) then
365403 return
366404 end
367405
@@ -371,13 +409,24 @@ function M.set_folds(fold_ranges)
371409
372410 vim .api .nvim_win_call (win , function ()
373411 local view = vim .fn .winsaveview ()
374- vim .cmd (' silent! normal! zX' )
375- for _ , range in ipairs (folds ) do
376- local is_open = was_open [range .from ]
377- local cmd = is_open and ' zo' or ' zc'
412+ vim .cmd (' silent! normal! zx' )
413+ local prev_starts = {}
414+ for _ , start_line in ipairs (prev_folds .starts ) do
415+ prev_starts [start_line ] = true
416+ end
378417
379- vim .fn .cursor (range .from , 1 )
380- vim .cmd (' silent! normal! ' .. cmd )
418+ for _ , range in ipairs (folds .ranges ) do
419+ if not prev_starts [range .from ] then
420+ vim .fn .cursor (range .from , 1 )
421+ vim .cmd (' silent! normal! zc' )
422+ end
423+ end
424+
425+ for _ , range in ipairs (folds .ranges ) do
426+ if was_open [range .from ] then
427+ vim .fn .cursor (range .from , 1 )
428+ vim .cmd (' silent! normal! zo' )
429+ end
381430 end
382431
383432 vim .fn .winrestview (view )
@@ -393,10 +442,8 @@ function M.shift_folds(start_line, delta)
393442 return
394443 end
395444 local buf = windows .output_buf
396- local ok , folds = pcall (vim .api .nvim_buf_get_var , buf , ' opencode_folds' )
397- if not ok or not folds then
398- return
399- end
445+ local fold_state = get_fold_state (buf )
446+ local folds = fold_state .ranges
400447
401448 for _ , range in ipairs (folds ) do
402449 if range .from > start_line then
@@ -415,6 +462,15 @@ function M.shift_folds(start_line, delta)
415462 range .to = range .from
416463 end
417464 end
465+
466+ table.sort (folds , function (a , b )
467+ return a .from < b .from
468+ end )
469+
470+ fold_state .starts = {}
471+ for _ , range in ipairs (folds ) do
472+ fold_state .starts [# fold_state .starts + 1 ] = range .from
473+ end
418474end
419475
420476--- @return integer
0 commit comments