Skip to main content

scrive_core/
fold_map.rs

1//! Code folding — **bracket-anchored**. A fold is a collapsed bracket
2//! *pair*, identified by the byte offset of its **opener** (`([{`). That offset
3//! is the only thing stored; it rides each committed transaction's [`Patch`] via
4//! [`Patch::map_offset`], and the fold's *extent* (which rows are hidden) is
5//! re-derived from the live [`Brackets`] pass every frame.
6//!
7//! Storing only the opener (never a hidden-interior byte range) is what keeps a
8//! fold from drifting onto an unrelated span: there is no interior offset to go
9//! stale. Reconcile is then trivial — a fold survives iff its opener is still a
10//! live, foldable open bracket. Rows are reconstructed each frame, so there is no
11//! persisted visual index to desync.
12//!
13//! **View state, not document state:** folds never enter the buffer or the undo
14//! stack; they compose over the buffer as a second display layer the
15//! render/movement/hit-test paths consult.
16
17use crate::bracket::Brackets;
18use crate::buffer::Buffer;
19use crate::coords::{Bias, Point};
20use crate::display_map::{BufferRow, DisplayRow};
21use crate::offset_set::OffsetSet;
22use crate::patch::Patch;
23use crate::sum_tree::{Dimension, Item, SumTree, Summary};
24
25// Op-count canary: the number of full `FoldMap::new` builds on this thread. The
26// per-keystroke analog of the widget's draw-budget gate — a document-scale test
27// asserts a plain keystroke / arrow key does NOT rebuild the whole map (which is
28// O(folds)); it shifts the cache in place or reads it. Thread-local so parallel
29// tests (which build fresh maps constantly) don't race the count. Debug/test
30// only; zero-cost in release.
31#[cfg(any(test, debug_assertions))]
32thread_local! {
33    pub(crate) static FOLD_BUILDS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
34}
35
36// Op-count canary: `FoldSet::reconcile` scans (the O(folds) retain) on this
37// thread. A test asserts a plain keystroke / Enter (no bracket char changed, so
38// no fold can lose its pair) does NOT scan the fold set, while a brace deletion
39// does. Debug/test only; zero-cost in release.
40#[cfg(any(test, debug_assertions))]
41thread_local! {
42    pub(crate) static RECONCILE_SCANS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
43}
44
45/// The persistent, document-owned set of active folds. Each entry is the byte
46/// offset of one collapsed pair's **opening bracket**, kept sorted and unique.
47/// Rides [`Patch`] remap in the commit path (see [`FoldSet::apply_patch`]); the
48/// extent is resolved from the [`Brackets`] pass in [`FoldMap::new`], so folds
49/// survive edits and undo/redo with no interior bookkeeping to drift.
50#[derive(Clone, Default, Debug)]
51pub struct FoldSet {
52    /// Collapsed pairs' opening-bracket byte offsets, on a delta-gap SumTree
53    /// ([`OffsetSet`]) so a text edit shifts the whole set without the flat Vec's
54    /// O(folds) rebase; membership / window / first-at are O(log n).
55    offsets: OffsetSet,
56    /// Bumped on every mutation — the memoization key for the document's cached
57    /// [`FoldMap`] (folds are view state, so an edit's buffer revision does NOT
58    /// cover a pure fold toggle; this does).
59    generation: u64,
60}
61
62impl FoldSet {
63    /// An empty fold set.
64    #[must_use]
65    pub fn new() -> Self {
66        Self::default()
67    }
68
69    /// Whether nothing is folded.
70    #[must_use]
71    pub fn is_empty(&self) -> bool {
72        self.offsets.is_empty()
73    }
74
75    /// The number of active folds.
76    #[must_use]
77    pub fn len(&self) -> usize {
78        self.offsets.len()
79    }
80
81    /// The smallest folded opener at byte `offset` or after, if any — an O(log)
82    /// probe into the opener-sorted set (e.g. "the fold headed on this row"),
83    /// not a scan of every fold.
84    #[must_use]
85    pub fn first_at_or_after(&self, offset: u32) -> Option<u32> {
86        self.offsets.first_at_or_after(offset)
87    }
88
89    /// The mutation counter — the document's `FoldMap` cache invalidates when it
90    /// changes (see the struct field).
91    #[must_use]
92    pub fn generation(&self) -> u64 {
93        self.generation
94    }
95
96    /// Note a mutation for the cache key.
97    fn bump(&mut self) {
98        self.generation = self.generation.wrapping_add(1);
99    }
100
101    /// Unfold everything.
102    pub fn clear(&mut self) {
103        self.offsets = OffsetSet::new();
104        self.bump();
105    }
106
107    /// Collapse the pair whose opener is at byte offset `open`. No-op returning
108    /// `false` if that opener is already folded. The caller vouches that `open` is
109    /// a foldable open bracket (see [`crate::Document::fold`]).
110    pub fn fold(&mut self, open: u32) -> bool {
111        if self.offsets.insert(open) {
112            self.bump();
113            true
114        } else {
115            false
116        }
117    }
118
119    /// Unfold the pair whose opener is at `open`. Returns whether one was removed.
120    pub fn unfold(&mut self, open: u32) -> bool {
121        if self.offsets.remove(open) {
122            self.bump();
123            true
124        } else {
125            false
126        }
127    }
128
129    /// Collapse every opener in `opens` in ONE pass — one set mutation for the
130    /// whole batch. A multi-caret `Ctrl+Shift+[` can collapse thousands of pairs at
131    /// once; calling [`Self::fold`] per item would be O(folds) each and O(folds²)
132    /// overall, so the batch path keeps a document-scale collapse linear. The
133    /// caller vouches each offset is a foldable open bracket.
134    pub fn fold_all(&mut self, opens: impl IntoIterator<Item = u32>) {
135        for open in opens {
136            self.offsets.insert(open); // idempotent; dedups
137        }
138        self.bump();
139    }
140
141    /// Unfold every opener in `opens` in ONE pass — the batch mirror of
142    /// [`Self::unfold`].
143    pub fn unfold_all(&mut self, opens: &[u32]) {
144        if opens.is_empty() {
145            return;
146        }
147        self.offsets.remove_all(opens);
148        self.bump();
149    }
150
151    /// Whether the pair opening at `open` is folded.
152    #[must_use]
153    pub fn is_folded(&self, open: u32) -> bool {
154        self.offsets.contains(open)
155    }
156
157    /// Fold the pair at `open` if not folded, else unfold it.
158    pub fn toggle(&mut self, open: u32) -> bool {
159        if self.is_folded(open) {
160            self.unfold(open)
161        } else {
162            self.fold(open)
163        }
164    }
165
166    /// Shift every opener through a committed patch. Bias `Right` keeps the
167    /// offset tracking its bracket char (an insertion at the bracket pushes both
168    /// right together). An edit that deletes the opener maps it into the deletion;
169    /// [`FoldSet::reconcile`] then drops it (it is no longer a live bracket).
170    pub fn apply_patch(&mut self, patch: &Patch) {
171        // Shift every opener through the patch (Bias::Right — the offset tracks its
172        // bracket char) on the delta-gap tree; a deleted opener maps into the
173        // deletion and reconcile drops it.
174        self.offsets.apply_patch(patch);
175        // Deliberately does NOT bump the generation: shifting openers through an
176        // edit is not a structural change to the fold SET, and the generation is
177        // the document's `FoldMap` cache key. The edit path shifts the cached
178        // map in lockstep via `FoldMap::apply_patch`, so a rebuild is reserved for
179        // real fold add/remove (fold/unfold/reconcile), which do bump.
180    }
181
182    /// Drop every fold whose opener is no longer a live *foldable* open bracket.
183    /// `is_foldable_opener` answers "is this byte offset an open bracket with a
184    /// matched partner forming a foldable range?" (from the live [`Brackets`]
185    /// pass — see `Document::reconcile_folds`). Because the extent is
186    /// re-derived, there is nothing else to validate or heal.
187    pub fn reconcile(&mut self, is_foldable_opener: impl Fn(u32) -> bool) {
188        #[cfg(any(test, debug_assertions))]
189        RECONCILE_SCANS.with(|c| c.set(c.get() + 1));
190        crate::perf::charge(self.offsets.len() as u64); // complexity gate: whole-set scan
191        let drop: Vec<u32> =
192            self.offsets.offsets().into_iter().filter(|&o| !is_foldable_opener(o)).collect();
193        self.offsets.remove_all(&drop);
194        self.bump();
195    }
196
197    /// The collapsed openers whose offset lies in `range`, ascending — the folds an
198    /// edit's re-matched region could directly touch. O(log n + hits).
199    #[must_use]
200    pub fn openers_in(&self, range: core::ops::Range<u32>) -> Vec<u32> {
201        self.offsets.in_range(range.start, range.end)
202    }
203
204    /// Windowed reconcile ([`reconcile`](Self::reconcile)'s twin): drop only the
205    /// `candidates` (a small, pre-narrowed set) that are no longer foldable. The
206    /// common case — a bracket edit that broke no fold — removes nothing and costs
207    /// only the O(candidates) predicate; the O(folds) `retain` runs (and is
208    /// counted) ONLY when a fold actually broke, which is the unavoidable Vec
209    /// compaction. Callers vouch every candidate is currently folded.
210    pub fn reconcile_only(&mut self, candidates: &[u32], is_foldable_opener: impl Fn(u32) -> bool) {
211        let drop: Vec<u32> = candidates.iter().copied().filter(|&o| !is_foldable_opener(o)).collect();
212        if drop.is_empty() {
213            return; // no fold broke — O(candidates), no whole-set touch
214        }
215        #[cfg(any(test, debug_assertions))]
216        RECONCILE_SCANS.with(|c| c.set(c.get() + 1));
217        crate::perf::charge(self.offsets.len() as u64); // complexity gate: fold-set touch
218        self.offsets.remove_all(&drop);
219        self.bump();
220    }
221
222    /// The collapsed openers (offsets), ascending — for [`FoldMap`] and tests.
223    pub fn iter(&self) -> impl Iterator<Item = u32> + '_ {
224        self.offsets.offsets().into_iter()
225    }
226}
227
228/// One resolved block fold: it hides buffer rows `header+1..=last`.
229#[derive(Clone, Copy, PartialEq, Eq, Debug)]
230struct FoldedRegion {
231    header: u32,
232    last: u32,
233}
234
235/// One resolved **inline** fold — a collapsed single-line bracket pair. It hides
236/// the bytes *between* the brackets (`open+1..close`) on one still-visible row,
237/// rendered `[ … ]`. The delimiters stay; only the interior collapses, so `close`
238/// remains a real, addressable buffer position.
239#[derive(Clone, Copy, PartialEq, Eq, Debug)]
240pub struct InlineFold {
241    /// The buffer row the pair sits on (opener and closer share it).
242    pub row: u32,
243    /// Byte offset of the opening bracket.
244    pub open: u32,
245    /// Byte offset of the closing bracket.
246    pub close: u32,
247}
248
249impl InlineFold {
250    /// The LEFT landable edge of the collapsed gap: just after the `[`.
251    #[must_use]
252    pub fn left_edge(&self) -> u32 {
253        crate::row_layout::gap_left_edge(self.open)
254    }
255
256    /// The RIGHT landable edge: the closing bracket itself.
257    #[must_use]
258    pub fn right_edge(&self) -> u32 {
259        self.close
260    }
261
262    /// Whether caret offset `off` is strictly inside the collapsed gap — the one
263    /// caret-boundary rule shared by movement, the document's fold-time caret
264    /// pull-out, and the widget's projections.
265    #[must_use]
266    pub fn hides_caret_at(&self, off: u32) -> bool {
267        crate::row_layout::gap_hides_caret(self.open, self.close, off)
268    }
269
270    /// Whether the *glyph* at `off` is hidden by this fold — the deliberately
271    /// off-by-one sibling of [`Self::hides_caret_at`]: the glyph at `open+1`
272    /// hides even though its caret slot stays landable.
273    #[must_use]
274    pub fn hides_glyph_at(&self, off: u32) -> bool {
275        crate::row_layout::gap_hides_glyph(self.open, self.close, off)
276    }
277}
278
279/// Reduce a laminar set of block regions to its **root** (outermost) members,
280/// sorted ascending by header and pairwise disjoint. A region nested inside
281/// another hides no extra rows and its header is itself hidden, so only roots
282/// drive the row arithmetic. Bracket pairs are naturally laminar (nested or
283/// disjoint, never crossing), so this never has to reject a crossing.
284fn root_regions(mut regions: Vec<FoldedRegion>) -> Vec<FoldedRegion> {
285    regions.sort_by_key(|r| (r.header, core::cmp::Reverse(r.last)));
286    let mut roots: Vec<FoldedRegion> = Vec::new();
287    for r in regions {
288        // `r.header > prev_root.last` ⇒ disjoint (a new root); otherwise `r` sits
289        // inside the current root's row span (laminar ⇒ fully contained) — skip.
290        if roots.last().is_none_or(|root| r.header > root.last) {
291            roots.push(r);
292        }
293    }
294    roots
295}
296
297/// Reduce inline folds to their **roots**: drop any inline fold nested inside
298/// another collapsed inline fold on the same row — its interior is already hidden
299/// in the outer fold's `…` chip, so rendering it separately would double-count the
300/// horizontal collapse (and, on the no-span path, slice a backwards range). Bracket
301/// pairs are laminar, so an inner fold is fully contained
302/// (`outer.open < inner.open && inner.close < outer.close`). The inline analog of
303/// [`root_regions`]: sort outer-before-inner, keep a fold unless the last root
304/// encloses it (the last root is always the tightest enclosing candidate, since a
305/// container and all its contents sort before any disjoint sibling).
306/// Delta-gap encode root-reduced, opener-ascending inline folds into the tree.
307fn build_inline(folds: Vec<InlineFold>) -> SumTree<InlineItem> {
308    let (mut prev_open, mut prev_row) = (0u32, 0u32);
309    let items = folds.into_iter().map(move |f| {
310        debug_assert!(f.open >= prev_open && f.row >= prev_row, "inline folds ascend");
311        let it = InlineItem { open_gap: f.open - prev_open, width: f.close - f.open, row_gap: f.row - prev_row };
312        prev_open = f.open;
313        prev_row = f.row;
314        it
315    });
316    SumTree::from_items(items)
317}
318
319fn root_inline(mut inline: Vec<InlineFold>) -> Vec<InlineFold> {
320    inline.sort_by(|a, b| a.open.cmp(&b.open).then(b.close.cmp(&a.close)));
321    let mut roots: Vec<InlineFold> = Vec::new();
322    for f in inline {
323        if roots.last().is_none_or(|r| !(r.row == f.row && r.open < f.open && f.close < r.close)) {
324            roots.push(f);
325        }
326    }
327    roots
328}
329
330/// A visible display row, resolved back to its buffer row — the render driver.
331#[derive(Clone, Copy, PartialEq, Eq, Debug)]
332pub struct VisibleRow {
333    /// The row's index in display space.
334    pub display_row: DisplayRow,
335    /// The buffer row it renders.
336    pub buffer_row: BufferRow,
337    /// Whether this row is a fold header (draw a chevron + `…` chip).
338    pub is_fold_header: bool,
339    /// On a header, the last folded row — `Some(last)` ⇒ this fold is collapsed.
340    pub last_folded: Option<BufferRow>,
341}
342
343/// One resolved block fold on the delta-gap region tree. `vgap` = this region's
344/// header row minus the PREVIOUS region's `last` (the visible rows between them;
345/// ≥ 1 for every region after the first, since roots are disjoint — 0 only for a
346/// fold headed on row 0). `span` = `last − header` (the hidden interior rows, ≥ 1
347/// for a block). Absolute header/last are prefix sums, so a line insert shifts a
348/// whole suffix by reanchoring ONE seam gap.
349#[derive(Clone, Copy, Debug)]
350struct Region {
351    vgap: u32,
352    span: u32,
353}
354
355/// Relative region summary. `vgap_sum` accumulates to a header's display row
356/// (`Σ vgap = header − hidden_before`), `span_sum` to hidden rows, and their sum to
357/// the absolute `last` row — the three dimensions the projections seek by.
358#[derive(Clone, Copy, Debug, Default)]
359struct RegionSummary {
360    vgap_sum: u32,
361    span_sum: u32,
362    count: u32,
363}
364
365impl Summary for RegionSummary {
366    fn add_summary(&mut self, o: &Self) {
367        self.vgap_sum += o.vgap_sum;
368        self.span_sum += o.span_sum;
369        self.count += o.count;
370    }
371}
372
373impl Item for Region {
374    type Summary = RegionSummary;
375    fn summary(&self) -> RegionSummary {
376        RegionSummary { vgap_sum: self.vgap, span_sum: self.span, count: 1 }
377    }
378}
379
380/// Absolute `last` row — `Σ (vgap + span)`; the cumulative end after region `i` is
381/// `last_i`, so `seek(LastDim(r))` finds the first region whose `last > r`.
382#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
383struct LastDim(u32);
384impl Dimension<RegionSummary> for LastDim {
385    fn add_summary(&mut self, s: &RegionSummary) {
386        self.0 += s.vgap_sum + s.span_sum;
387    }
388}
389
390/// A header's display row — `Σ vgap`; the cumulative end after region `i` is
391/// `header_display[i]` (strictly increasing), so `summary_before(DispDim(d)).count`
392/// = the headers displayed strictly above `d`.
393#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
394struct DispDim(u32);
395impl Dimension<RegionSummary> for DispDim {
396    fn add_summary(&mut self, s: &RegionSummary) {
397        self.0 += s.vgap_sum;
398    }
399}
400
401/// Hidden rows — `Σ span`; accumulated before region `i` it is `hidden_before[i]`.
402#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
403struct HiddenDim(u32);
404impl Dimension<RegionSummary> for HiddenDim {
405    fn add_summary(&mut self, s: &RegionSummary) {
406        self.0 += s.span_sum;
407    }
408}
409
410/// Region index.
411#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
412struct RCount(u32);
413impl Dimension<RegionSummary> for RCount {
414    fn add_summary(&mut self, s: &RegionSummary) {
415        self.0 += s.count;
416    }
417}
418
419/// A located region decoded to absolute rows plus the rows hidden before it — the
420/// shared result of the buffer-row → region seek (see [`FoldMap::locate`]).
421struct Located {
422    header: u32,
423    last: u32,
424    span: u32,
425    hidden_before: u32,
426}
427
428/// Delta-gap inline (single-line) fold leaf, on the same contract as [`Region`].
429/// `open_gap` = open − previous fold's open, `width` = close − open, `row_gap` =
430/// row − previous fold's row. Absolute open/row are prefix sums; close = open +
431/// width. So a text edit shifts the whole inline set by reanchoring one seam.
432#[derive(Clone, Copy, Debug)]
433struct InlineItem {
434    open_gap: u32,
435    width: u32,
436    row_gap: u32,
437}
438
439#[derive(Clone, Copy, Debug, Default)]
440struct InlineSummary {
441    open_span: u32,
442    row_span: u32,
443    count: u32,
444}
445
446impl Summary for InlineSummary {
447    fn add_summary(&mut self, o: &Self) {
448        self.open_span += o.open_span;
449        self.row_span += o.row_span;
450        self.count += o.count;
451    }
452}
453
454impl Item for InlineItem {
455    type Summary = InlineSummary;
456    fn summary(&self) -> InlineSummary {
457        InlineSummary { open_span: self.open_gap, row_span: self.row_gap, count: 1 }
458    }
459}
460
461/// Combined open-offset / buffer-row dimension: accumulates BOTH so a single seek
462/// or visit yields a fold's `open` and `row` together (one dimension slot). Ordered
463/// by `(open, row)` — open dominates, and rows rise monotonically with opens, so it
464/// is a total order in document order, and `summary_before(OpenRow { open: x, .. })`
465/// counts the folds with `open < x`.
466#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
467struct OpenRow {
468    open: u32,
469    row: u32,
470}
471impl Dimension<InlineSummary> for OpenRow {
472    fn add_summary(&mut self, s: &InlineSummary) {
473        self.open += s.open_span;
474        self.row += s.row_span;
475    }
476}
477
478/// Inline fold index.
479#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
480struct ICount(u32);
481impl Dimension<InlineSummary> for ICount {
482    fn add_summary(&mut self, s: &InlineSummary) {
483        self.0 += s.count;
484    }
485}
486
487/// A fold-aware row converter — buffer rows ↔ display rows with folded interiors
488/// hidden. Resolved from a [`FoldSet`] + the live [`Brackets`] + buffer,
489/// **memoized** on the [`Document`](crate::Document). A fold add/remove
490/// ([`FoldSet::generation`] bump) rebuilds it from scratch; every text edit shifts
491/// it in place through the patch ([`Self::apply_patch`]) — a no-line-change edit
492/// only moves inline offsets, and a line insert/remove reanchors the region tree's
493/// affected seam — so plain typing over a document-scale fold set never pays an
494/// O(folds) rebuild. Manual `PartialEq` (decoded regions) so a test can deep-equal
495/// the incrementally-shifted map against a fresh build (the drift oracle).
496#[derive(Debug)]
497pub struct FoldMap {
498    /// Resolved *root* block folds (sorted, disjoint by header) on a delta-gap
499    /// [`SumTree`] keyed by header row: a line insert/remove shifts a whole suffix
500    /// in O(log n) by reanchoring one seam gap, and every row projection is an
501    /// O(log n) cursor descent, not an O(folds) full rebuild.
502    regions: SumTree<Region>,
503    /// Resolved inline (single-line) folds on a delta-gap [`SumTree`], keyed by
504    /// opener offset (hence by row), so a text edit reanchors one seam in O(log n)
505    /// rather than an O(inline) rebase, and offset/row queries are O(log n).
506    inline: SumTree<InlineItem>,
507    buffer_row_count: u32,
508}
509
510impl PartialEq for FoldMap {
511    fn eq(&self, other: &Self) -> bool {
512        self.buffer_row_count == other.buffer_row_count
513            && self.decoded_regions() == other.decoded_regions()
514            && self.decoded_inline() == other.decoded_inline()
515    }
516}
517impl Eq for FoldMap {}
518
519impl FoldMap {
520    /// Resolve the collapsed openers into visible block-fold regions against the
521    /// live `brackets` + `buffer`. `O(folds log brackets)`. An opener that is no
522    /// longer a matched bracket, or whose pair is single-line, yields no block
523    /// region (the latter is a future inline fold, not a row-hider).
524    #[must_use]
525    pub fn new(folds: &FoldSet, brackets: &Brackets, buffer: &Buffer) -> Self {
526        #[cfg(any(test, debug_assertions))]
527        FOLD_BUILDS.with(|c| c.set(c.get() + 1));
528        let mut regions = Vec::new();
529        let mut inline = Vec::new();
530        for open in folds.iter() {
531            // `foldable_partner`, not `at().partner`: the depth-free lookup, so a
532            // rebuild over many folds never allocates a prefix stack per opener.
533            let Some(close) = brackets.foldable_partner(open) else { continue };
534            let header = buffer.offset_to_point(open).row;
535            let last = buffer.offset_to_point(close).row;
536            if last > header {
537                regions.push(FoldedRegion { header, last }); // multi-line ⇒ block
538            } else if crate::row_layout::pair_has_interior(open, close) {
539                inline.push(InlineFold { row: header, open, close }); // single-line ⇒ inline
540            }
541        }
542        Self::assemble(regions, inline, buffer.line_count())
543    }
544
545    /// Shift this map through a committed edit's `patch` IN PLACE, returning
546    /// `false` (→ the caller rebuilds instead) when the edit changed the line
547    /// count. A line insertion/removal moves block-region rows and the display
548    /// prefix sums, which is not a cheap in-place update; a **no-line-change**
549    /// edit (plain typing) leaves every block region, prefix sum, and the buffer
550    /// row count unchanged and only moves inline-fold offsets — so this is
551    /// O(inline folds), and O(0) for the common block-only "collapse all" case,
552    /// far cheaper than the O(folds) rebuild the per-keystroke edit path would
553    /// otherwise pay.
554    ///
555    /// A fold whose *interior* an edit touches is expanded (removed) before this
556    /// runs, so a surviving inline fold's interior is intact — only its (and its
557    /// `close`'s) offset shifts, exactly as [`FoldSet::apply_patch`] shifts the
558    /// opener, keeping the two in lockstep. Fold add/remove bumps the generation
559    /// and forces a rebuild, so this never has to reconcile the fold set.
560    #[must_use]
561    pub fn apply_patch(&mut self, patch: &Patch, buffer: &Buffer) -> bool {
562        let new_rows = buffer.line_count();
563        let d = i64::from(new_rows) - i64::from(self.buffer_row_count);
564        // A line change moves block-region rows. A SINGLE edit shifts every region
565        // (and inline fold) at/after the insertion row by `+d` — the region tree in
566        // O(log n) via one reanchored seam, inline offsets/rows in the loop below.
567        // A MULTI-edit line change is a piecewise row shift (multi-caret, rare) —
568        // rebuild. `os` bounds the inline row shift to the folds after the edit.
569        if d != 0 {
570            let [edit] = patch.edits() else { return false };
571            let (os, oe) = (edit.old.start, edit.old.end);
572            // A line change overlapping an inline fold's span can RECLASSIFY it
573            // (single-line ↔ multi-line block) — a shift can't express that, and
574            // `expand` doesn't catch an inline fold's byte interior, so rebuild.
575            if self.inline_overlaps(os, oe) {
576                return false;
577            }
578            // The edit's OLD range covered rows `[r_os, r_oe_old]`; content ending
579            // after `r_os` shifts by `d`. `r_oe_old = new-end row − d` recovers the
580            // old-end row without the old buffer (the net line delta `d` is the
581            // newlines the range gained). For an insertion `r_oe_old == r_os`.
582            let r_os = buffer.offset_to_point(edit.new.start).row;
583            let r_oe_old = (i64::from(buffer.offset_to_point(edit.new.end).row) - d) as u32;
584            // Regions that DON'T shift end at/before `r_os` (`last ≤ r_os`): a fold
585            // tailed on `r_os` has its opener earlier, so it is before the edit.
586            let k = self.regions.summary_before(&LastDim(r_os.saturating_add(1))).count;
587            let (before, suffix) = self.regions.split_at(&RCount(k));
588            if !suffix.is_empty() {
589                let last_prev = before.extent::<LastDim>().0; // last of the region before the seam
590                let (first, ..) = suffix.seek::<RCount, LastDim>(&RCount(0)).expect("non-empty");
591                let header = last_prev + first.vgap;
592                // If the edit's OLD range reached INTO the first shifted region
593                // (`header < r_oe_old`), a deletion shrank its span — `expand` misses
594                // that (it checks only the collapsed NEW endpoint, which can land on
595                // a still-visible row), and a rigid row shift can't express a span
596                // change — so rebuild. Later regions have larger headers, so probing
597                // the first suffices. (An insertion has `r_oe_old == r_os ≤ header`,
598                // so this never trips — surviving folds never straddle an insertion.)
599                if header < r_oe_old {
600                    return false;
601                }
602                // Reanchor: the first shifted region's `vgap` absorbs `+d` (its
603                // header moves while its predecessor, in `before`, stayed); later
604                // gaps/spans are relative and unchanged. The straddle guard makes
605                // the new header ≥ r_os ≥ the predecessor's last, so `vgap` stays ≥ 0.
606                let new_vgap = i64::from(first.vgap) + d;
607                debug_assert!(new_vgap >= 0, "a non-straddling shift keeps vgap ≥ 0");
608                let fixed = Region { vgap: new_vgap as u32, span: first.span };
609                let suffix = suffix.replace(RCount(0)..RCount(1), std::iter::once(fixed));
610                self.regions = before.append(&suffix);
611            }
612            self.buffer_row_count = new_rows;
613        }
614        self.shift_inline(patch, d);
615        true
616    }
617
618    /// Shift the inline-fold tree through `patch`. A single edit whose range
619    /// overlaps no surviving inline fold reanchors ONE seam in O(log n) — folds
620    /// after the edit move `open += byte_delta`, `row += d`. Otherwise (a straddling
621    /// edit that changes a fold's width, or a multi-edit) it rides each fold through
622    /// the patch and rebuilds — O(inline), the rare fallback. A line-changing
623    /// straddle already forced a full rebuild upstream, so the fallback is `d == 0`.
624    fn shift_inline(&mut self, patch: &Patch, d: i64) {
625        if let [edit] = patch.edits() {
626            let (os, oe) = (edit.old.start, edit.old.end);
627            if !self.inline_overlaps(os, oe) {
628                let byte_delta = (i64::from(edit.new.end) - i64::from(edit.new.start))
629                    - (i64::from(oe) - i64::from(os));
630                let k = self.inline.summary_before(&OpenRow { open: os, row: 0 }).count; // open < os
631                let (before, suffix) = self.inline.split_at(&ICount(k));
632                if !suffix.is_empty() {
633                    let (it, ..) = suffix.seek::<ICount, OpenRow>(&ICount(0)).expect("non-empty");
634                    let fixed = InlineItem {
635                        open_gap: (i64::from(it.open_gap) + byte_delta) as u32,
636                        width: it.width,
637                        row_gap: (i64::from(it.row_gap) + d) as u32,
638                    };
639                    let suffix = suffix.replace(ICount(0)..ICount(1), std::iter::once(fixed));
640                    self.inline = before.append(&suffix);
641                }
642                return;
643            }
644        }
645        debug_assert!(d == 0, "the inline fallback is only reached on a no-line-change edit");
646        let mut v = self.decoded_inline();
647        for f in &mut v {
648            f.open = patch.map_offset(f.open, Bias::Right);
649            f.close = patch.map_offset(f.close, Bias::Right);
650        }
651        self.inline = build_inline(v);
652    }
653
654    /// Reduce raw folds to roots and build the O(log) search prefixes (the
655    /// hidden-row prefix sum and each header's display row). The one owner of the
656    /// [`FoldMap`] invariants, shared by [`Self::new`] and the test constructor.
657    fn assemble(regions: Vec<FoldedRegion>, inline: Vec<InlineFold>, buffer_row_count: u32) -> Self {
658        let regions = root_regions(regions); // sorted, disjoint by header
659        // Delta-gap encode: vgap = header − previous region's last (visible rows
660        // between them), span = last − header (hidden interior rows).
661        let mut prev_last = 0u32;
662        let mut items = Vec::with_capacity(regions.len());
663        for r in &regions {
664            items.push(Region { vgap: r.header - prev_last, span: r.last - r.header });
665            prev_last = r.last;
666        }
667        Self {
668            regions: SumTree::from_items(items),
669            inline: build_inline(root_inline(inline)),
670            buffer_row_count,
671        }
672    }
673
674    /// Decode the region tree back to absolute `(header, last)` pairs — the O(n)
675    /// reduce for `PartialEq` (the drift oracle) and tests.
676    fn decoded_regions(&self) -> Vec<(u32, u32)> {
677        let mut out = Vec::with_capacity(self.regions.summary().count as usize);
678        let mut last = 0u32;
679        for item in self.regions.items() {
680            let header = last + item.vgap;
681            last = header + item.span;
682            out.push((header, last));
683        }
684        out
685    }
686
687    /// Decode the inline-fold tree to absolute [`InlineFold`]s, ascending by opener
688    /// — the O(n) reduce for `PartialEq`, [`Self::inline_folds`], and tests.
689    fn decoded_inline(&self) -> Vec<InlineFold> {
690        let mut out = Vec::with_capacity(self.inline.summary().count as usize);
691        let (mut open, mut row) = (0u32, 0u32);
692        for it in self.inline.items() {
693            crate::perf::charge(1); // complexity gate: whole inline-set decode is O(F)
694            open += it.open_gap;
695            row += it.row_gap;
696            out.push(InlineFold { row, open, close: open + it.width });
697        }
698        out
699    }
700
701    /// The last inline fold opening strictly before `off` (the only one that can
702    /// hide `off`, since roots are disjoint) — O(log n). The offset-keyed lookup
703    /// movement / hit-testing / the caret-eject path use.
704    #[must_use]
705    pub fn inline_fold_before(&self, off: u32) -> Option<InlineFold> {
706        let j = self.inline.summary_before(&OpenRow { open: off, row: 0 }).count; // open < off
707        let (it, _c, OpenRow { open: ob, row: rb }) =
708            self.inline.seek::<ICount, OpenRow>(&ICount(j.checked_sub(1)?))?;
709        let open = ob + it.open_gap;
710        Some(InlineFold { row: rb + it.row_gap, open, close: open + it.width })
711    }
712
713    /// The root inline fold opening *exactly* at `opener`, or `None` — O(log n)
714    /// membership for the collapsed-chip hit-test. Roots are disjoint and
715    /// open-sorted, so [`Self::inline_fold_before`]`(opener+1)` returns the last
716    /// root with `open ≤ opener`; testing `open == opener` is then exact root
717    /// membership. `saturating_add` guards the u32 edge; a nested (non-root)
718    /// opener resolves to its enclosing root, whose `open != opener` → `None` —
719    /// exactly what a linear `inline_folds().binary_search` membership test yields.
720    #[must_use]
721    pub fn inline_fold_at(&self, opener: u32) -> Option<InlineFold> {
722        self.inline_fold_before(opener.saturating_add(1)).filter(|f| f.open == opener)
723    }
724
725    /// Whether any inline fold's `[open, close]` overlaps `[os, oe]` — O(log n). One
726    /// probe suffices: the fold with the greatest `open ≤ oe` has the greatest
727    /// `close` (disjoint, open-sorted ⇒ close ascending).
728    fn inline_overlaps(&self, os: u32, oe: u32) -> bool {
729        let j = self.inline.summary_before(&OpenRow { open: oe.saturating_add(1), row: 0 }).count;
730        let Some(k) = j.checked_sub(1) else { return false };
731        let Some((it, _c, OpenRow { open: ob, .. })) = self.inline.seek::<ICount, OpenRow>(&ICount(k))
732        else {
733            return false;
734        };
735        ob + it.open_gap + it.width >= os // close ≥ os
736    }
737
738    /// The region whose `[last_prev, last]` brackets buffer row `r` — the first with
739    /// `last > r`, or the last region as a fallback (then `r` is beyond every fold) —
740    /// decoded to absolute rows plus the hidden rows before it. `None` iff no folds.
741    /// The shared O(log n) descent under every buffer-row → display query.
742    fn locate(&self, r: u32) -> Option<Located> {
743        let (item, LastDim(last_prev), HiddenDim(hidden_before)) =
744            self.regions.seek::<LastDim, HiddenDim>(&LastDim(r))?;
745        let header = last_prev + item.vgap;
746        Some(Located { header, last: header + item.span, span: item.span, hidden_before })
747    }
748
749    /// Tail-inclusive [`Self::locate`]: the region with `last >= r` (via
750    /// `seek(last > r − 1)`), so a fold's TAIL row (`last == r`) resolves to ITS
751    /// fold, not the next one below it. `locate`'s `last > r` is right for the
752    /// display projections but off-by-one for containment (`is_folded` /
753    /// `fold_containing`), where the hidden tail row must still count as folded.
754    fn containing(&self, r: u32) -> Option<Located> {
755        let (item, LastDim(last_prev), HiddenDim(hidden_before)) =
756            self.regions.seek::<LastDim, HiddenDim>(&LastDim(r.saturating_sub(1)))?;
757        let header = last_prev + item.vgap;
758        Some(Located { header, last: header + item.span, span: item.span, hidden_before })
759    }
760
761    /// An empty (nothing-folded) map — the document's cache seed before its first
762    /// real build.
763    #[must_use]
764    pub(crate) fn empty() -> Self {
765        Self::assemble(Vec::new(), Vec::new(), 0)
766    }
767
768    /// The resolved inline (single-line) folds, sorted by opener offset — the
769    /// render / caret / hit-test consult these to collapse a mid-line span.
770    #[must_use]
771    pub fn inline_folds(&self) -> Vec<InlineFold> {
772        self.decoded_inline()
773    }
774
775    /// The inline folds on buffer `row`, ascending by opener — the per-row render
776    /// query (`RowLayout`). O(log n + hits): a row's folds are a contiguous window.
777    #[must_use]
778    pub fn inline_folds_on_row(&self, row: u32) -> Vec<InlineFold> {
779        let mut out = Vec::new();
780        self.inline.filter_visit::<OpenRow, _, _>(
781            &|before: &OpenRow, sum: &InlineSummary| {
782                before.row <= row && before.row + sum.row_span >= row
783            },
784            &mut |it: &InlineItem, before: &OpenRow| {
785                let r = before.row + it.row_gap;
786                if r == row {
787                    let open = before.open + it.open_gap;
788                    out.push(InlineFold { row: r, open, close: open + it.width });
789                }
790            },
791        );
792        out
793    }
794
795    /// Total hidden rows across all root folds (the tree root summary, O(1)).
796    fn hidden_total(&self) -> u32 {
797        self.regions.summary().span_sum
798    }
799
800    /// Number of display rows (buffer rows minus every hidden interior row).
801    #[must_use]
802    pub fn display_row_count(&self) -> u32 {
803        self.buffer_row_count - self.hidden_total()
804    }
805
806    /// The last valid display row.
807    #[must_use]
808    pub fn max_display_row(&self) -> DisplayRow {
809        DisplayRow(self.display_row_count().saturating_sub(1))
810    }
811
812    /// Buffer row → display row. A hidden interior row clips to its header's
813    /// display row, so a collapsed fold's interior renders at the header line.
814    #[must_use]
815    pub fn to_display_row(&self, row: BufferRow) -> DisplayRow {
816        let r = row.0;
817        let Some(l) = self.locate(r) else { return DisplayRow(r) };
818        if r > l.header && r <= l.last {
819            DisplayRow(l.header - l.hidden_before) // interior → clip to the header's display row
820        } else if r <= l.header {
821            DisplayRow(r - l.hidden_before) // visible, at/before this region's header
822        } else {
823            DisplayRow(r - l.hidden_before - l.span) // r beyond the last fold (seek fallback)
824        }
825    }
826
827    /// Display row → buffer row. Always a visible row (a header or an unfolded
828    /// line), never a hidden interior.
829    #[must_use]
830    pub fn to_buffer_row(&self, row: DisplayRow) -> BufferRow {
831        let d = row.0;
832        // Regions whose header displays strictly above `d` are hidden-above; add
833        // their hidden rows back. `summary_before(DispDim(d))` = exactly those
834        // (header_display[i] < d), `.span_sum` = their hidden rows (= hidden_before[j]).
835        BufferRow(d + self.regions.summary_before(&DispDim(d)).span_sum)
836    }
837
838    /// Whether `row` is a hidden interior row of some fold.
839    #[must_use]
840    pub fn is_folded(&self, row: BufferRow) -> bool {
841        let r = row.0;
842        self.containing(r).is_some_and(|l| l.header < r && r <= l.last)
843    }
844
845    /// If `offset` is HIDDEN inside a collapsed fold, the caret's eject target — a
846    /// block's header line end, or an inline gap's left landable edge; `None` when
847    /// visible. `O(log folds)` (regions and inline roots are sorted and disjoint),
848    /// so a multi-caret fold pulls every hidden caret out in one `O(carets·log
849    /// folds)` pass instead of an `O(carets·folds)` [`Self::display_position`]
850    /// probe each — the "collapse all" ejection at document scale.
851    #[must_use]
852    pub fn entry_edge_if_hidden(&self, buffer: &Buffer, offset: u32) -> Option<u32> {
853        // Block: is `offset`'s row STRICTLY inside a region's hidden interior?
854        // (The tail row `last` rides the header and stays visible, so `< last`.)
855        let row = buffer.offset_to_point(offset).row;
856        if let Some(l) = self.locate(row) {
857            if l.header < row && row < l.last {
858                let end = buffer.line_len(l.header);
859                return Some(buffer.point_to_offset(Point::new(l.header, end)));
860            }
861        }
862        // Inline: is `offset` strictly inside a collapsed single-line gap? Only the
863        // fold opening just before it can hide it (roots are disjoint).
864        if let Some(f) = self.inline_fold_before(offset) {
865            if f.hides_caret_at(offset) {
866                return Some(f.left_edge());
867            }
868        }
869        None
870    }
871
872    /// The collapsed root fold whose hidden interior contains `row` (i.e.
873    /// `header < row <= last`), as `(header, last)`. `None` if `row` is a header
874    /// or not folded. Used by fold-aware horizontal movement to hop the collapsed
875    /// gap between the header line and the inline closing tail.
876    #[must_use]
877    pub fn fold_containing(&self, row: BufferRow) -> Option<(BufferRow, BufferRow)> {
878        let r = row.0;
879        self.containing(r)
880            .filter(|l| l.header < r && r <= l.last)
881            .map(|l| (BufferRow(l.header), BufferRow(l.last)))
882    }
883
884    /// If `row` is a folded header, the fold's last row (for the chip/chevron and
885    /// placeholder). `None` if `row` is not a header of a collapsed fold.
886    #[must_use]
887    pub fn fold_at_header(&self, row: BufferRow) -> Option<BufferRow> {
888        // The region bracketing `row` is the one headed there iff its header == row
889        // (a header's own `last > row`, and the prior region's `last < row`).
890        self.locate(row.0).filter(|l| l.header == row.0).map(|l| BufferRow(l.last))
891    }
892
893    /// If `row` is the *last* (closing) row of a collapsed root fold, that fold's
894    /// header row — the display anchor for the fold's inline closing tail.
895    /// `None` otherwise. Roots are disjoint, so at most one fold ends on `row`.
896    /// The inverse direction of [`Self::fold_at_header`], for placing a caret /
897    /// hit-testing a click on the collapsed line's real closing bracket.
898    #[must_use]
899    pub fn header_of_tail(&self, row: BufferRow) -> Option<BufferRow> {
900        // Roots are disjoint ⇒ `last` is strictly increasing. `j` regions end before
901        // `row`; the j-th (first with `last >= row`) is tailed on `row` iff its
902        // `last == row`, and then its header is `last − span`.
903        let r = row.0;
904        let j = self.regions.summary_before(&LastDim(r)).count;
905        let (item, RCount(_), LastDim(last_prev)) = self.regions.seek::<RCount, LastDim>(&RCount(j))?;
906        let last = last_prev + item.vgap + item.span;
907        (last == r).then(|| BufferRow(last - item.span))
908    }
909
910    /// The display-row window covering fractional rows `[top_rows, bottom_rows)`
911    /// from the content top: floor the first visible row, ceil past the last,
912    /// clamp to the row count. THE one owner of the render window rule — the
913    /// widget derives the fractional rows from pixels ([`Self::display_row_at`]
914    /// is its single-row sibling) and never floors/clamps itself.
915    /// (`f64` like [`Self::display_row_at`]: exact for every u32 row count,
916    /// where `f32` fractional rows degrade past ~2²³.)
917    #[must_use]
918    pub fn display_window(&self, top_rows: f64, bottom_rows: f64) -> core::ops::Range<u32> {
919        let first = top_rows.floor().max(0.0) as u32;
920        let last = (bottom_rows.ceil().max(0.0) as u32).min(self.display_row_count());
921        first..last.max(first)
922    }
923
924    /// The visible display rows in `window` (see [`Self::display_window`]),
925    /// each resolved to its buffer row with the fold-header flag — the render
926    /// loop's driver. Hidden interior rows are simply not produced.
927    pub fn visible_rows(&self, window: core::ops::Range<u32>) -> impl Iterator<Item = VisibleRow> + '_ {
928        window.map(move |d| {
929            let buffer_row = self.to_buffer_row(DisplayRow(d));
930            let last_folded = self.fold_at_header(buffer_row);
931            VisibleRow {
932                display_row: DisplayRow(d),
933                buffer_row,
934                is_fold_header: last_folded.is_some(),
935                last_folded,
936            }
937        })
938    }
939
940    /// Build a `FoldMap` straight from `(header, last)` block regions — the
941    /// row-arithmetic under test, without needing a bracketed buffer.
942    #[cfg(test)]
943    pub(crate) fn from_rows(regions: impl IntoIterator<Item = (u32, u32)>, row_count: u32) -> Self {
944        let regions = regions
945            .into_iter()
946            .filter(|&(h, l)| l > h)
947            .map(|(header, last)| FoldedRegion { header, last })
948            .collect();
949        Self::assemble(regions, Vec::new(), row_count)
950    }
951
952    /// Build a `FoldMap` from `(row, open, close)` inline folds — the inline
953    /// row/offset arithmetic under test, without a bracketed buffer.
954    #[cfg(test)]
955    pub(crate) fn from_inline(
956        folds: impl IntoIterator<Item = (u32, u32, u32)>,
957        row_count: u32,
958    ) -> Self {
959        let inline = folds.into_iter().map(|(row, open, close)| InlineFold { row, open, close }).collect();
960        Self::assemble(Vec::new(), inline, row_count)
961    }
962}
963
964#[cfg(test)]
965mod tests {
966    use super::*;
967    use crate::patch::{Edit, Patch};
968
969    // ── FoldSet: opener-keyed set that rides the patch mover ──
970
971    #[test]
972    fn fold_toggle_and_duplicate() {
973        let mut f = FoldSet::new();
974        assert!(f.fold(10));
975        assert!(!f.fold(10), "same opener already folded");
976        assert!(f.is_folded(10));
977        assert!(f.toggle(10)); // unfolds
978        assert!(!f.is_folded(10));
979        assert!(f.is_empty());
980    }
981
982    #[test]
983    fn apply_patch_shifts_openers() {
984        let mut f = FoldSet::new();
985        f.fold(5);
986        f.fold(20);
987        // Insert 3 bytes at the top → both openers shift right by 3.
988        f.apply_patch(&Patch::single(Edit { old: 0..0, new: 0..3 }));
989        assert_eq!(f.iter().collect::<Vec<_>>(), vec![8, 23]);
990    }
991
992    #[test]
993    fn reconcile_drops_openers_that_are_no_longer_foldable() {
994        let mut f = FoldSet::new();
995        f.fold(8);
996        f.fold(23);
997        // Only offset 8 is still a live foldable open bracket.
998        f.reconcile(|o| o == 8);
999        assert_eq!(f.iter().collect::<Vec<_>>(), vec![8]);
1000    }
1001
1002    #[test]
1003    fn deleting_the_opener_reconciles_away() {
1004        let mut f = FoldSet::new();
1005        f.fold(4);
1006        // Delete the bracket char at [4,5); the opener maps into the deletion.
1007        f.apply_patch(&Patch::single(Edit { old: 4..5, new: 4..4 }));
1008        f.reconcile(|_| false); // nothing foldable there anymore
1009        assert!(f.is_empty());
1010    }
1011
1012    // ── FoldMap: row arithmetic over resolved block regions ──
1013
1014    #[test]
1015    fn empty_is_identity() {
1016        let m = FoldMap::from_rows([], 6);
1017        assert_eq!(m.display_row_count(), 6);
1018        for r in 0..6 {
1019            assert_eq!(m.to_display_row(BufferRow(r)), DisplayRow(r));
1020            assert_eq!(m.to_buffer_row(DisplayRow(r)), BufferRow(r));
1021            assert!(!m.is_folded(BufferRow(r)));
1022        }
1023    }
1024
1025    #[test]
1026    fn hides_interior_rows() {
1027        let m = FoldMap::from_rows([(2, 5)], 10); // hides rows 3,4,5
1028        assert_eq!(m.display_row_count(), 7);
1029        assert!(m.is_folded(BufferRow(3)) && m.is_folded(BufferRow(5)));
1030        assert!(!m.is_folded(BufferRow(2)) && !m.is_folded(BufferRow(6)));
1031        assert_eq!(m.fold_at_header(BufferRow(2)), Some(BufferRow(5)));
1032    }
1033
1034    #[test]
1035    fn display_and_buffer_row_mapping() {
1036        let m = FoldMap::from_rows([(2, 5)], 10);
1037        for r in [3, 4, 5] {
1038            assert_eq!(m.to_display_row(BufferRow(r)), m.to_display_row(BufferRow(2)));
1039        }
1040        assert_eq!(m.to_display_row(BufferRow(6)), DisplayRow(3)); // 6 − 3 hidden
1041        assert_eq!(m.to_buffer_row(DisplayRow(2)), BufferRow(2)); // the header
1042        assert_eq!(m.to_buffer_row(DisplayRow(3)), BufferRow(6)); // interior skipped
1043        assert_eq!(m.to_buffer_row(DisplayRow(6)), BufferRow(9));
1044    }
1045
1046    #[test]
1047    fn roundtrip_visible_rows() {
1048        let m = FoldMap::from_rows([(2, 5)], 10);
1049        let all: Vec<_> = m.visible_rows(0..m.display_row_count()).collect();
1050        assert_eq!(all.iter().map(|v| v.buffer_row.0).collect::<Vec<_>>(), vec![0, 1, 2, 6, 7, 8, 9]);
1051        let header = all.iter().find(|v| v.is_fold_header).unwrap();
1052        assert_eq!((header.buffer_row, header.last_folded), (BufferRow(2), Some(BufferRow(5))));
1053    }
1054
1055    #[test]
1056    fn nested_reduces_to_root() {
1057        let m = FoldMap::from_rows([(1, 8), (3, 5)], 10); // inner (3,5) ⊆ outer (1,8)
1058        assert_eq!(m.display_row_count(), 3); // rows 2..=8 hidden ⇒ visible 0,1,9
1059        assert_eq!(m.fold_at_header(BufferRow(1)), Some(BufferRow(8)));
1060        assert_eq!(m.fold_at_header(BufferRow(3)), None, "inner header is hidden, not a root");
1061        let visible: Vec<_> = m.visible_rows(0..m.display_row_count()).map(|v| v.buffer_row.0).collect();
1062        assert_eq!(visible, vec![0, 1, 9]);
1063    }
1064
1065    #[test]
1066    fn three_level_nesting_reduces_to_root() {
1067        let m = FoldMap::from_rows([(0, 10), (2, 8), (4, 6)], 12);
1068        assert_eq!(m.display_row_count(), 2); // root (0,10) hides 1..=10 ⇒ visible 0,11
1069        assert_eq!(m.to_buffer_row(DisplayRow(1)), BufferRow(11));
1070    }
1071
1072    #[test]
1073    fn disjoint_siblings_both_hide() {
1074        let m = FoldMap::from_rows([(2, 5), (6, 8)], 10);
1075        assert_eq!(m.display_row_count(), 10 - 3 - 2);
1076        assert_eq!(m.fold_at_header(BufferRow(2)), Some(BufferRow(5)));
1077        assert_eq!(m.fold_at_header(BufferRow(6)), Some(BufferRow(8)));
1078    }
1079
1080    #[test]
1081    fn fold_at_eof() {
1082        let m = FoldMap::from_rows([(3, 5)], 6);
1083        assert_eq!(m.display_row_count(), 4); // rows 4,5 hidden
1084        assert_eq!(m.fold_at_header(BufferRow(3)), Some(BufferRow(5)));
1085    }
1086
1087    #[test]
1088    fn header_of_tail_is_the_inverse_of_fold_at_header() {
1089        let m = FoldMap::from_rows([(2, 5)], 10);
1090        assert_eq!(m.fold_at_header(BufferRow(2)), Some(BufferRow(5)));
1091        assert_eq!(m.header_of_tail(BufferRow(5)), Some(BufferRow(2)));
1092        assert_eq!(m.header_of_tail(BufferRow(2)), None, "the header is not a tail");
1093        assert_eq!(m.header_of_tail(BufferRow(4)), None, "an interior row is not the last");
1094    }
1095
1096    #[test]
1097    fn fold_containing_finds_the_enclosing_root() {
1098        let m = FoldMap::from_rows([(2, 5)], 10);
1099        assert_eq!(m.fold_containing(BufferRow(4)), Some((BufferRow(2), BufferRow(5))));
1100        assert_eq!(m.fold_containing(BufferRow(2)), None, "the header is visible, not contained");
1101        assert_eq!(m.fold_containing(BufferRow(6)), None);
1102    }
1103
1104    #[test]
1105    fn inline_shift_is_sublinear_in_fold_count() {
1106        use crate::sum_tree::NODE_ALLOCS;
1107        // N inline folds, one per row. A single front insert shifts ALL of them
1108        // (open += 1) — one reanchored delta-gap seam, O(log N) node allocs, not the
1109        // O(N) rebuild the flat Vec (or the fallback) would do. A `d == 0` edit keeps
1110        // it on the inline path only (no block regions, no offset_to_point).
1111        let allocs_for = |n: u32| -> f64 {
1112            let mut m = FoldMap::from_inline((0..n).map(|i| (i, i * 10 + 100, i * 10 + 103)), n);
1113            let buf = Buffer::new(&"\n".repeat((n - 1) as usize)).unwrap(); // line_count == n
1114            let patch = Patch::single(Edit { old: 5..5, new: 5..6 }); // insert 1 byte, no newline
1115            NODE_ALLOCS.with(|c| c.set(0));
1116            let _ = m.apply_patch(&patch, &buf);
1117            NODE_ALLOCS.with(std::cell::Cell::get) as f64
1118        };
1119        let (small, big) = (allocs_for(1000), allocs_for(4000));
1120        eprintln!("[fold_map] inline shift node allocs {small} -> {big}  ({:.2}x)", big / small);
1121        assert!(
1122            big <= small * 2.0,
1123            "inline shift allocates superlinearly ({small} -> {big} nodes): it rebuilt the \
1124             whole inline set instead of reanchoring the suffix at one seam"
1125        );
1126    }
1127
1128    #[test]
1129    fn a_hidden_tail_row_counts_as_folded_with_a_fold_below() {
1130        // `is_folded`/`fold_containing` use the TAIL-inclusive lookup so a fold's
1131        // hidden tail row still counts as folded even when a second root sits
1132        // directly below it. Row 5 is fold (2,5)'s hidden tail; root (6,8) follows.
1133        // A plain `last > r` lookup would resolve row 5 to (6,8) and report it visible.
1134        let m = FoldMap::from_rows([(2, 5), (6, 8)], 10);
1135        assert!(m.is_folded(BufferRow(5)), "a fold's tail row is hidden");
1136        assert_eq!(m.fold_containing(BufferRow(5)), Some((BufferRow(2), BufferRow(5))));
1137        // The display projection agrees — row 5 collapses onto the header (row 2).
1138        assert_eq!(m.to_display_row(BufferRow(5)), m.to_display_row(BufferRow(2)));
1139        assert!(!m.is_folded(BufferRow(2)), "the header is visible");
1140        assert!(m.is_folded(BufferRow(7)) && m.is_folded(BufferRow(8)), "second fold hidden");
1141    }
1142
1143    // ── inline root reduction: a collapsed inline pair nested in another ──
1144
1145    #[test]
1146    fn nested_inline_folds_reduce_to_the_outer_root() {
1147        // One row: `( … )` at [4,30] with `[ … ]` at [20,28] nested inside it.
1148        let roots = root_inline(vec![
1149            InlineFold { row: 0, open: 20, close: 28 },
1150            InlineFold { row: 0, open: 4, close: 30 },
1151        ]);
1152        assert_eq!(roots.iter().map(|f| f.open).collect::<Vec<_>>(), vec![4], "only the outer chip renders");
1153    }
1154
1155    #[test]
1156    fn disjoint_inline_folds_both_survive() {
1157        let roots = root_inline(vec![
1158            InlineFold { row: 0, open: 12, close: 18 },
1159            InlineFold { row: 0, open: 4, close: 8 },
1160        ]);
1161        assert_eq!(roots.iter().map(|f| f.open).collect::<Vec<_>>(), vec![4, 12]);
1162    }
1163
1164    #[test]
1165    fn triple_nested_inline_reduces_to_root() {
1166        // A(0..40) ⊃ B(5..30) ⊃ C(10..20), all on row 0 → only A survives.
1167        let roots = root_inline(vec![
1168            InlineFold { row: 0, open: 10, close: 20 },
1169            InlineFold { row: 0, open: 5, close: 30 },
1170            InlineFold { row: 0, open: 0, close: 40 },
1171        ]);
1172        assert_eq!(roots.iter().map(|f| f.open).collect::<Vec<_>>(), vec![0]);
1173    }
1174
1175    // ── the collapsed-chip inline-membership hit-test ───────────────────────────
1176
1177    #[test]
1178    fn inline_fold_at_matches_the_linear_membership() {
1179        // Parity oracle: over random inline-fold sets (disjoint + nested, several
1180        // rows) the O(log F) offset-keyed `inline_fold_at` descent must agree with the
1181        // linear `inline_folds()` membership scan — presence bit AND resolved fold —
1182        // at EVERY candidate offset (root openers, nested/reduced openers, non-openers).
1183        // This pins the `+1` and the `f.open == opener` filter (drop either and a
1184        // root/non-opener boundary diverges here). Row bands (open = row*100 + …) keep
1185        // opener-ascending ⟺ row-ascending, the real-buffer invariant `from_inline`
1186        // asserts; openers are deduped because a bracket char occupies one offset.
1187        let mut seed = 0x9E37_79B9_7F4A_7C15u64;
1188        let mut rng = || {
1189            seed ^= seed << 13;
1190            seed ^= seed >> 7;
1191            seed ^= seed << 17;
1192            seed
1193        };
1194        for _ in 0..300 {
1195            let n = (rng() % 9) as usize;
1196            let mut raw: Vec<(u32, u32, u32)> = Vec::new();
1197            for _ in 0..n {
1198                let row = (rng() % 4) as u32;
1199                let open = row * 100 + (rng() % 80) as u32;
1200                let width = 1 + (rng() % 18) as u32;
1201                raw.push((row, open, open + width));
1202            }
1203            raw.sort_unstable();
1204            raw.dedup_by_key(|&mut (_, o, _)| o); // unique openers, as real brackets are
1205            let m = FoldMap::from_inline(raw.iter().copied(), 4);
1206            let roots = m.inline_folds();
1207            for o in 0..=400u32 {
1208                let linear = roots.iter().find(|f| f.open == o).copied();
1209                let via_log = m.inline_fold_at(o);
1210                assert_eq!(
1211                    via_log, linear,
1212                    "inline_fold_at({o}) = {via_log:?} but linear membership = {linear:?}\nroots = {roots:?}"
1213                );
1214            }
1215            // The u32 edge: `saturating_add(1)` must not panic or false-match at MAX.
1216            assert_eq!(m.inline_fold_at(u32::MAX), roots.iter().find(|f| f.open == u32::MAX).copied());
1217        }
1218    }
1219
1220    #[test]
1221    fn inline_membership_is_fold_count_independent() {
1222        // `inline_fold_at` is an O(log F) offset-keyed descent, so its work meter
1223        // must stay flat as the inline-fold count doubles. A membership test that
1224        // decoded every inline leaf (`inline_folds().binary_search`) would charge F
1225        // per item in `decoded_inline`; the `sum_tree` seek under `inline_fold_before`
1226        // is unmetered, so the O(log) path charges ~0. One disjoint fold per row;
1227        // probe the middle opener (a real opener, so both the seek and a whole-set
1228        // scan do their full work).
1229        let meter_for = |f: u32| -> u64 {
1230            let m = FoldMap::from_inline((0..f).map(|i| (i, i * 10 + 100, i * 10 + 104)), f);
1231            let mid = (f / 2) * 10 + 100;
1232            crate::perf::reset();
1233            let _ = m.inline_fold_at(mid);
1234            crate::perf::meter()
1235        };
1236        let (small, big) = (meter_for(1000), meter_for(2000));
1237        eprintln!("[fold_map] inline membership meter {small} -> {big}");
1238        assert!(
1239            big <= small + small / 4 + 256,
1240            "inline_fold_at charged {small} -> {big}: membership decoded the whole inline set \
1241             instead of an O(log F) offset-keyed descent"
1242        );
1243    }
1244}