Skip to main content

scrive_core/
bracket.rs

1//! Bracket matching — the shared structural pass behind bracket-pair
2//! colorization, matching-bracket highlight, indent guides, and folding.
3//! `()`/`[]`/`{}` pair by a stack automaton (quotes are not nestable), yielding
4//! each bracket's nesting depth and matched-partner offset and flagging the
5//! unmatched.
6//!
7//! Matches **all** brackets regardless of string or comment context: this is a
8//! purely structural scan with no lexer state, so a `)` inside a string still
9//! counts as a bracket. Excluding string/comment brackets would need a scope
10//! class threaded through from the highlight layer, which carries only a
11//! resolved color, not lexical context.
12//!
13//! [`Brackets`] is a thin façade over one `SumTree<BracketItem>` (see
14//! `bracket_tree`) that stores only PRIMARY facts — each bracket's char and
15//! delta-gap position. Every derived fact a caller reads — `open`, `depth`,
16//! `partner`, "what pair encloses this" — is recomputed by an O(log) cursor
17//! descent, so no cached copy can drift, and an edit is a pure
18//! `SumTree::replace` of the edited byte range with
19//! the rescanned brackets. Because no partner offset is stored, there is nothing
20//! to repair across the edit boundary — the tree is always internally
21//! consistent by construction. [`Brackets::match_text`] is the O(n) load-time
22//! constructor (and the tests' oracle); [`Brackets::apply_edit`] rides one
23//! committed patch through the same tree, on the forward path and on every
24//! undo/redo step alike.
25
26use std::ops::Range;
27
28use crate::bracket_tree::{self, BracketItem};
29use crate::buffer::Buffer;
30use crate::patch::Patch;
31use crate::sum_tree::SumTree;
32
33// Op-count canary: how many `enclosing_or_touching` walks ran on this thread. A
34// document-scale multi-caret edit must trigger O(1) of them inside
35// `expand_folds_touched` (one, for the first edit point), NOT one per caret, so a
36// select-all → fold → type sequence stays cheap regardless of caret count.
37// Debug/test only; zero-cost in release.
38#[cfg(any(test, debug_assertions))]
39thread_local! {
40    pub(crate) static ENCLOSING_WALKS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
41}
42
43/// Whether `c` is any bracket char (opener or closer), as a byte — the document's
44/// reconcile gate ("did this edit add or remove a bracket?").
45pub(crate) fn is_bracket_byte(c: u8) -> bool {
46    matches!(c, b'(' | b')' | b'[' | b']' | b'{' | b'}')
47}
48
49/// One bracket occurrence in the document — fully derived on demand (nothing here
50/// is stored; the tree holds only the char and position).
51#[derive(Copy, Clone, PartialEq, Eq, Debug)]
52pub struct Bracket {
53    /// Byte offset of the bracket char.
54    pub offset: u32,
55    /// Whether it is an opener (`([{`) vs a closer (`)]}`).
56    pub open: bool,
57    /// Nesting depth (0 = outermost).
58    pub depth: u32,
59    /// The matched partner's offset, or `None` if unmatched.
60    pub partner: Option<u32>,
61}
62
63/// Every bracket in a document, riding one augmented `SumTree` that is the sole
64/// owner of position. Queries are O(log) descents; an edit is a
65/// `SumTree::replace` of the edited byte range.
66///
67/// `Default` is an EMPTY tree — valid both for the read-only query surface
68/// (movement's fold-free helper pairs it with an empty `FoldSet`) and as a base
69/// for [`Brackets::apply_edit`], since the tree needs no scaffold to seed: the
70/// first edit simply replaces an empty range.
71#[derive(Clone, Debug, Default)]
72pub struct Brackets {
73    tree: SumTree<BracketItem>,
74}
75
76impl Brackets {
77    /// Match every `()`/`[]`/`{}` bracket in `text` (LF-only — the buffer
78    /// contract) — the O(n) from-scratch constructor (document load; the tests'
79    /// oracle). Per-edit maintenance goes through [`Brackets::apply_edit`].
80    #[must_use]
81    pub fn match_text(text: &str) -> Self {
82        Self { tree: bracket_tree::tree_from_text(text) }
83    }
84
85    /// All brackets, in document order — the O(n) cold path (fold-source scans, the
86    /// capture-folds harness, tests). Hot paths use [`Self::in_range`].
87    #[must_use]
88    pub fn all(&self) -> Vec<Bracket> {
89        bracket_tree::derive_all(&self.tree)
90    }
91
92    /// The bracket at exactly `offset`, if any. O(log).
93    #[must_use]
94    pub fn at(&self, offset: u32) -> Option<Bracket> {
95        bracket_tree::at(&self.tree, offset)
96    }
97
98    /// The matched partner of the OPEN bracket at `offset`, or `None` if it is not a
99    /// live opener or is unmatched — the fold hot-path lookup (fold reconcile,
100    /// expand-on-hidden-edit, block/inline resolution). Unlike [`Self::at`] it never
101    /// resolves `depth`, so it costs O(log + partner fold) with no `ShapeSummary`
102    /// allocation — the difference between a fold-heavy keystroke being O(folds·log)
103    /// and an allocation storm. Callers that only need "is this opener's pair still
104    /// there, and where does it close?" must use this, never `at().partner`.
105    #[must_use]
106    pub fn foldable_partner(&self, offset: u32) -> Option<u32> {
107        bracket_tree::foldable_partner(&self.tree, offset)
108    }
109
110    /// The brackets whose offset lies in `range` (half-open: start inclusive, end
111    /// exclusive), in order, collected — the cold/slice callers (fold-pair
112    /// derivation, tests). An inverted range is empty, not a panic. Per-frame hot
113    /// paths iterate [`Self::in_range_iter`] instead, which allocates nothing.
114    #[must_use]
115    pub fn in_range(&self, range: Range<u32>) -> Vec<Bracket> {
116        self.in_range_iter(range).collect()
117    }
118
119    /// [`Self::in_range`] as a lazy iterator — the per-visible-row colorization
120    /// loop iterates this directly, so a frame's bracket colouring allocates no
121    /// `Vec` (one per visible row × every frame otherwise). An inverted range
122    /// yields nothing (the count bounds cross), matching [`Self::in_range`].
123    pub fn in_range_iter(&self, range: Range<u32>) -> impl Iterator<Item = Bracket> + '_ {
124        bracket_tree::in_range(&self.tree, range.start, range.end)
125    }
126
127    /// The matched bracket pair to highlight for a caret at `caret`, as
128    /// `(bracket_offset, partner_offset)`. The bracket directly *left* of the
129    /// caret is preferred (the one you just typed past); the one directly right
130    /// is the fallback. `None` if the caret isn't adjacent to a *matched* bracket.
131    #[must_use]
132    pub fn active_pair(&self, caret: u32) -> Option<(u32, u32)> {
133        bracket_tree::active_pair(&self.tree, caret)
134    }
135
136    /// The innermost matched pair whose interior contains `caret` (opener offset
137    /// `< caret <=` closer offset), as `(open_offset, close_offset)` — the *active*
138    /// indent guide. `None` if the caret is inside no matched pair.
139    #[must_use]
140    pub fn enclosing_pair(&self, caret: u32) -> Option<(u32, u32)> {
141        bracket_tree::enclosing_pairs(&self.tree, caret).into_iter().next()
142    }
143
144    /// The opener of the innermost pair strictly enclosing `caret`
145    /// (`open < caret <= close`) that satisfies `pred`. `None` if none matches.
146    #[must_use]
147    pub fn innermost_enclosing_where(&self, caret: u32, pred: impl Fn(u32, u32) -> bool) -> Option<u32> {
148        bracket_tree::enclosing_pairs(&self.tree, caret)
149            .into_iter()
150            .find(|&(o, c)| pred(o, c))
151            .map(|(o, _)| o)
152    }
153
154    /// The innermost matched pair whose CONTENTS contain the whole range —
155    /// `open + 1 <= start && end <= close` — as `(open, close)`. The
156    /// expand-selection ladder's next rung. `None` outside every pair.
157    #[must_use]
158    pub fn enclosing_pair_of_range(&self, start: u32, end: u32) -> Option<(u32, u32)> {
159        // The enclosers of `start` are innermost-first (closes widen outward), so
160        // the first whose closer also reaches `end` is the tightest fit.
161        bracket_tree::enclosing_pairs(&self.tree, start).into_iter().find(|&(_, close)| end <= close)
162    }
163
164    /// Every matched pair `(open, close)` with `open <= caret <= close + 1` —
165    /// the pairs enclosing the caret PLUS the ones it touches at either bracket
166    /// (caret AT an opener, or just past a closer) — the fold-at-caret candidate
167    /// set. Unordered; the caller picks the innermost.
168    #[must_use]
169    pub fn enclosing_or_touching(&self, caret: u32) -> Vec<(u32, u32)> {
170        #[cfg(any(test, debug_assertions))]
171        ENCLOSING_WALKS.with(|c| c.set(c.get() + 1));
172        bracket_tree::enclosing_or_touching_pairs(&self.tree, caret)
173    }
174
175    /// Splice this structure through one committed `patch` — the incremental twin
176    /// of [`Brackets::match_text`]. `buffer` is the POST-edit buffer and `patch`
177    /// the committed transaction's old→new byte ranges (`new` in final
178    /// coordinates) — exactly what `rebase_views` holds, on the forward path and on
179    /// every undo/redo step alike.
180    ///
181    /// Returns the reconcile window `[seed_lo, re)` in post-edit coordinates: the
182    /// re-matched span extended left to the outermost opener enclosing the first
183    /// edit (so a fold whose partner the edit deletes is covered), or `0..0` when
184    /// no bracket structure changed. `Document::reconcile_folds` re-checks only the
185    /// folds in that window.
186    ///
187    /// # Why the result deep-equals a from-scratch rebuild
188    ///
189    /// The tree stores only each bracket's char and delta-gap position; `open`,
190    /// `depth`, and `partner` are DERIVED per query from the always-current tree.
191    /// So an edit need only make the stored primary facts correct: rescan the
192    /// edited byte range into brackets, `SumTree::replace` them in, and — because
193    /// the encoding is delta-gap — re-anchor just the FIRST suffix bracket's gap
194    /// (the rest are relative and do not move). No cross-boundary partner pointer
195    /// is stored, so none can be left stale by the splice; the
196    /// incremental≡`match_text` oracle enforces the equivalence.
197    pub fn apply_edit(&mut self, patch: &Patch, buffer: &Buffer) -> Range<u32> {
198        let (tree, region) = bracket_tree::apply_edit(&self.tree, patch, buffer);
199        self.tree = tree;
200        region
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207    use crate::coords::Point;
208    use crate::transaction::{apply, EditOp};
209
210    fn bat(b: &Brackets, o: u32) -> Bracket {
211        b.at(o).expect("a bracket at that offset")
212    }
213
214    #[test]
215    fn matches_nested_pairs_with_depth() {
216        let b = Brackets::match_text("(a[b]{c})"); // ( [ ] { } ) at 0 2 4 5 7 8
217        assert_eq!((bat(&b, 0).partner, bat(&b, 0).depth), (Some(8), 0));
218        assert_eq!((bat(&b, 2).partner, bat(&b, 2).depth), (Some(4), 1));
219        assert_eq!((bat(&b, 5).partner, bat(&b, 5).depth), (Some(7), 1));
220        assert_eq!((bat(&b, 8).partner, bat(&b, 8).depth), (Some(0), 0));
221    }
222
223    #[test]
224    fn flags_unmatched_brackets() {
225        let b = Brackets::match_text("(]"); // opener never closes, closer has no opener
226        assert_eq!(bat(&b, 0).partner, None);
227        assert_eq!(bat(&b, 1).partner, None);
228    }
229
230    #[test]
231    fn active_pair_prefers_the_bracket_left_of_the_caret() {
232        let b = Brackets::match_text("(){}"); // '(' 0↔1  ')' 1↔0  '{' 2↔3  '}' 3↔2
233        assert_eq!(b.active_pair(0), Some((0, 1))); // no left → right '(' at 0
234        assert_eq!(b.active_pair(1), Some((0, 1))); // left '(' at 0 (preferred over ')')
235        assert_eq!(b.active_pair(2), Some((1, 0))); // left ')' at 1 (preferred over '{')
236        assert_eq!(b.active_pair(3), Some((2, 3))); // left '{' at 2
237        assert_eq!(b.active_pair(4), Some((3, 2))); // left '}' at 3
238        assert_eq!(Brackets::match_text("x").active_pair(1), None); // none adjacent
239    }
240
241    #[test]
242    fn enclosing_pair_is_the_innermost_containing_the_caret() {
243        let b = Brackets::match_text("(a[b]{c})"); // ( [ ] { } ) at 0 2 4 5 7 8
244        assert_eq!(b.enclosing_pair(1), Some((0, 8))); // inside only the outer ()
245        assert_eq!(b.enclosing_pair(3), Some((2, 4))); // inside [] (depth 1) beats ()
246        assert_eq!(b.enclosing_pair(6), Some((5, 7))); // inside {} (depth 1)
247        assert_eq!(b.enclosing_pair(0), None); // at the opener → not yet inside
248        assert_eq!(b.enclosing_pair(9), None); // past the closer
249    }
250
251    /// The enclosing queries must find the pair even when its opener is FAR to the
252    /// left with many sibling pairs in between (the O(brackets)-scan case the tree
253    /// descent replaces), and the touching cases (`caret == open`, `caret ==
254    /// close + 1`). An `enclosing_*` result independent of the sibling count is the
255    /// whole point.
256    #[test]
257    fn enclosing_walk_matches_a_brute_scan_with_siblings() {
258        //           0         1         2
259        //           0123456789012345678901234567
260        let b = Brackets::match_text("([1][2]{a(b)c}[3])xy");
261        // A brute-force oracle: innermost matched pair strictly containing c.
262        let brute_enclosing = |caret: u32| {
263            b.all()
264                .iter()
265                .filter_map(|br| br.partner.map(|p| (br.offset, p)).filter(|_| br.open))
266                .filter(|&(o, c)| o < caret && caret <= c)
267                .min_by_key(|&(o, c)| c - o)
268        };
269        for caret in 0..=20 {
270            assert_eq!(b.enclosing_pair(caret), brute_enclosing(caret), "enclosing_pair({caret})");
271        }
272        // Deep inside the nested `(b)` at offset 10 — enclosed by (9,11), {7,13),
273        // (0,17), reached past the [1] [2] siblings.
274        assert_eq!(b.enclosing_pair(10), Some((9, 11)));
275        // Just after the `}` at 13: caret 14 is back at the outer ( level.
276        assert_eq!(b.enclosing_pair(14), Some((0, 17)));
277        // enclosing_or_touching includes the touched pairs at either bracket.
278        let mut touch = b.enclosing_or_touching(9); // caret ON the `(` at 9
279        touch.sort_unstable();
280        assert!(touch.contains(&(9, 11)), "the pair opening at the caret is touched");
281        assert!(touch.contains(&(7, 13)) && touch.contains(&(0, 17)), "and its enclosers");
282        let after_close = b.enclosing_or_touching(12); // caret just past `)` at 11
283        assert!(after_close.contains(&(9, 11)), "the pair closing at caret-1 is touched");
284        // enclosing_pair_of_range: the tightest pair whose CONTENTS hold [start,end].
285        assert_eq!(b.enclosing_pair_of_range(10, 11), Some((9, 11))); // fits (b)
286        assert_eq!(b.enclosing_pair_of_range(9, 12), Some((7, 13))); // spans (b) → next out
287    }
288
289    #[test]
290    fn active_pair_skips_unmatched_brackets() {
291        let b = Brackets::match_text("(]"); // both unmatched
292        assert_eq!(b.active_pair(1), None); // left '(' unmatched, right ']' unmatched
293        assert_eq!(b.active_pair(2), None);
294    }
295
296    #[test]
297    fn quotes_are_not_brackets() {
298        let b = Brackets::match_text("\"()\""); // quotes ignored; ( ) matched
299        assert_eq!(b.all().len(), 2);
300        assert_eq!(bat(&b, 1).partner, Some(2));
301    }
302
303    #[test]
304    fn in_range_boundaries() {
305        let b = Brackets::match_text("(a[b]{c})"); // ( [ ] { } ) at 0 2 4 5 7 8
306        let offsets = |s: &[Bracket]| s.iter().map(|x| x.offset).collect::<Vec<_>>();
307        assert!(b.in_range(0..0).is_empty()); // empty range
308        assert!(b.in_range(3..3).is_empty());
309        assert_eq!(b.in_range(0..9), b.all()); // full range == all
310        assert_eq!(b.in_range(0..u32::MAX), b.all());
311        assert_eq!(offsets(&b.in_range(2..5)), vec![2, 4]); // start inclusive…
312        assert_eq!(offsets(&b.in_range(2..6)), vec![2, 4, 5]); // …end exclusive
313        assert_eq!(offsets(&b.in_range(8..9)), vec![8]); // exactly the last one
314        assert_eq!(offsets(&b.in_range(1..2)), Vec::<u32>::new()); // gap between brackets
315        #[allow(clippy::reversed_empty_ranges)]
316        let inverted = b.in_range(5..2);
317        assert!(inverted.is_empty()); // inverted range is empty, not a panic
318    }
319
320    // ───────────────────────── incremental engine ─────────────────────────
321
322    /// The oracle assertion: the incrementally-maintained structure must
323    /// deep-equal a from-scratch `match_text` of the same text — every derived
324    /// Bracket field. With no stored line shapes, `all()` (offset, open, depth,
325    /// partner) IS the whole observable state.
326    fn assert_oracle(b: &Brackets, buf: &Buffer, ctx: &str) {
327        let oracle = Brackets::match_text(&buf.text());
328        assert_eq!(b.all(), oracle.all(), "brackets diverged from a scratch rebuild: {ctx}");
329    }
330
331    /// Apply `ops` to the buffer and ride the committed patch through
332    /// `apply_edit` — the exact call shape `rebase_views` will use.
333    fn edit(b: &mut Brackets, buf: &mut Buffer, ops: Vec<EditOp>) -> Range<u32> {
334        let committed = apply(buf, ops).expect("test edits are disjoint and in-bounds");
335        b.apply_edit(committed.patch(), buf)
336    }
337
338    /// Oracle test: ~500 seeded-random edits (biased toward bracket chars,
339    /// newlines, mid-pair splits, multi-char inserts/deletes and the occasional
340    /// scattered two-edit transaction) over a bracket-heavy document; after every
341    /// commit the incremental structure must deep-equal a from-scratch rebuild.
342    /// This generator exercises every way an edit can re-pair brackets across the
343    /// splice boundary, so any divergence between incremental and scratch matching
344    /// surfaces here.
345    #[test]
346    fn incremental_matches_scratch_after_random_edits() {
347        let seed: u64 = 0x5EED_0BAD_F00D_2026;
348        let mut s = seed;
349        let mut rng = move || {
350            // xorshift64 — deterministic, no dev-dependency.
351            s ^= s << 13;
352            s ^= s >> 7;
353            s ^= s << 17;
354            s
355        };
356        let mut buf = Buffer::new(
357            "fn main() {\n    let v = vec![1, (2), [3]];\n    if (a[i]) { b(c[d]); }\n}\n",
358        )
359        .expect("fixture loads");
360        let mut b = Brackets::match_text(&buf.text());
361        const POOL: &[u8] = b"(){}[](){}[]\n\nab ";
362        for i in 0..500 {
363            let len = buf.len();
364            let kind = rng() % 12;
365            let ops = if kind < 5 {
366                let at = (rng() % (u64::from(len) + 1)) as u32;
367                let n = 1 + (rng() % 4) as usize;
368                let text: String =
369                    (0..n).map(|_| POOL[(rng() % POOL.len() as u64) as usize] as char).collect();
370                vec![EditOp::insert(at, text)]
371            } else if kind < 7 {
372                let a = (rng() % (u64::from(len) + 1)) as u32;
373                let e = (a + 1 + (rng() % 12) as u32).min(len);
374                vec![EditOp::delete(a..e)]
375            } else if kind < 9 {
376                let a = (rng() % (u64::from(len) + 1)) as u32;
377                let e = (a + (rng() % 6) as u32).min(len);
378                let n = 1 + (rng() % 5) as usize;
379                let text: String =
380                    (0..n).map(|_| POOL[(rng() % POOL.len() as u64) as usize] as char).collect();
381                vec![EditOp::new(a..e, text)]
382            } else if kind == 9 {
383                let a = (rng() % (u64::from(len) / 2 + 1)) as u32;
384                let c = (len / 2 + (rng() % (u64::from(len) / 2 + 1)) as u32).min(len);
385                vec![EditOp::insert(a, "("), EditOp::insert(c, ")")]
386            } else {
387                // Scattered structure-neutral inserts across the whole document —
388                // the O(N log) per-edit splice path over a MULTI-edit patch; must
389                // still deep-equal a scratch rebuild.
390                let n = 2 + (rng() % 3) as usize; // 2..=4 carets
391                let mut offs: Vec<u32> =
392                    (0..n).map(|_| (rng() % (u64::from(len) + 1)) as u32).collect();
393                offs.sort_unstable();
394                offs.dedup();
395                offs.into_iter().map(|o| EditOp::insert(o, "z")).collect()
396            };
397            let committed = apply(&mut buf, ops.clone()).expect("generated ops are disjoint");
398            b.apply_edit(committed.patch(), &buf);
399            let ctx = format!("edit {i} (seed {seed:#x}): {ops:?}");
400            assert_oracle(&b, &buf, &ctx);
401        }
402    }
403
404    // ── re-pairing cases: an edit changes which brackets pair with which. With
405    //    partners DERIVED from the always-current tree, each is just an ordinary
406    //    edit — the oracle confirms the derived partners follow the text ──
407
408    #[test]
409    fn prefix_opener_repoints_into_region() {
410        // "{\nx\n}", line 1 → "}": the prefix `{` re-points to the NEW line-1 closer,
411        // and the old line-2 `}` goes unmatched.
412        let mut buf = Buffer::new("{\nx\n}").expect("fixture loads");
413        let mut b = Brackets::match_text(&buf.text());
414        edit(&mut b, &mut buf, vec![EditOp::new(2..3, "}")]); // "{\n}\n}"
415        assert_oracle(&b, &buf, "line 1 x → }");
416        assert_eq!(bat(&b, 0).partner, Some(2));
417        assert_eq!(bat(&b, 2).partner, Some(0));
418        assert_eq!((bat(&b, 4).partner, bat(&b, 4).depth), (None, 0));
419    }
420
421    #[test]
422    fn converged_shape_different_identity() {
423        // "{\nx\n}", line 1 → "}{": the shape after line 1 is again `{`, but the
424        // identity behind it changed — the final `}` pairs the NEW region `{` at 3,
425        // and the prefix `{` re-points to the new line-1 `}`.
426        let mut buf = Buffer::new("{\nx\n}").expect("fixture loads");
427        let mut b = Brackets::match_text(&buf.text());
428        edit(&mut b, &mut buf, vec![EditOp::new(2..3, "}{")]); // "{\n}{\n}"
429        assert_oracle(&b, &buf, "line 1 x → }{");
430        assert_eq!(bat(&b, 0).partner, Some(2));
431        assert_eq!(bat(&b, 3).partner, Some(5));
432        assert_eq!(bat(&b, 5).partner, Some(3));
433    }
434
435    #[test]
436    fn converged_shape_crosses_into_the_suffix() {
437        // Same identity swap, but with a clean line between the edit and the final
438        // `}` — the `}` is a true suffix entry that must re-pair the region opener.
439        let mut buf = Buffer::new("{\nx\nq\n}").expect("fixture loads");
440        let mut b = Brackets::match_text(&buf.text());
441        edit(&mut b, &mut buf, vec![EditOp::new(2..3, "}{")]); // "{\n}{\nq\n}"
442        assert_oracle(&b, &buf, "line 1 x → }{ with a clean row before the closer");
443        assert_eq!(bat(&b, 0).partner, Some(2));
444        assert_eq!(bat(&b, 3).partner, Some(7));
445        assert_eq!(bat(&b, 7).partner, Some(3));
446    }
447
448    #[test]
449    fn crossing_repairs_mixed_seed_and_region_entries() {
450        // "{\n(\nb\n)}", line 1 → ")(": `)` pairs the region opener, `}` pairs the
451        // seed `{`, and the new line-1 `)` stays unmatched.
452        let mut buf = Buffer::new("{\n(\nb\n)}").expect("fixture loads");
453        let mut b = Brackets::match_text(&buf.text());
454        edit(&mut b, &mut buf, vec![EditOp::new(2..3, ")(")]); // "{\n)(\nb\n)}"
455        assert_oracle(&b, &buf, "mixed carried stack");
456        assert_eq!((bat(&b, 2).partner, bat(&b, 2).depth), (None, 0)); // unmatched )
457        assert_eq!(bat(&b, 3).partner, Some(7)); // region ( ↔ suffix )
458        assert_eq!(bat(&b, 7).partner, Some(3));
459        assert_eq!(bat(&b, 0).partner, Some(8)); // seed { ↔ suffix }
460        assert_eq!(bat(&b, 8).partner, Some(0));
461    }
462
463    #[test]
464    fn pair_spanning_edit_shifts_the_closer() {
465        // An edit strictly inside a multi-line pair's interior (no bracket chars
466        // touched): the closer's offset shifts by delta and the opener's DERIVED
467        // partner follows automatically (nothing stored to leave stale).
468        let mut buf = Buffer::new("{\naaa\nzzz\n}").expect("fixture loads");
469        let mut b = Brackets::match_text(&buf.text());
470        edit(&mut b, &mut buf, vec![EditOp::new(2..5, "aaaaa")]); // grow "aaa"→"aaaaa"
471        assert_oracle(&b, &buf, "interior growth inside a multi-line pair");
472        assert_eq!(bat(&b, 0).partner, Some(12)); // was Some(10)
473        assert_eq!(bat(&b, 12).partner, Some(0)); // the closer, shifted
474    }
475
476    #[test]
477    fn seed_leftover_cleared_to_none() {
478        // Delete a prefix opener's closer: the opener is now unmatched — with a
479        // derived partner there is no stale pointer to clear.
480        let mut buf = Buffer::new("{\n}").expect("fixture loads");
481        let mut b = Brackets::match_text(&buf.text());
482        edit(&mut b, &mut buf, vec![EditOp::delete(2..3)]); // "{\n"
483        assert_oracle(&b, &buf, "closer deleted");
484        assert_eq!(b.all().len(), 1);
485        assert_eq!((bat(&b, 0).partner, bat(&b, 0).open), (None, true));
486    }
487
488    // ── edits at scale: correctness deep in a large document (the incremental
489    //    splice is local by construction; the oracle proves it stays correct) ──
490
491    #[test]
492    fn keystroke_deep_in_2000_lines() {
493        let doc = vec!["fn f() { g(a[i], (b)); }"; 2000].join("\n");
494        let mut buf = Buffer::new(&doc).expect("fixture loads");
495        let mut b = Brackets::match_text(&buf.text());
496        let target = buf.point_to_offset(Point::new(1000, 3));
497        edit(&mut b, &mut buf, vec![EditOp::insert(target, "x")]);
498        assert_oracle(&b, &buf, "keystroke at line 1000");
499    }
500
501    #[test]
502    fn balanced_pair_insert_deep_in_a_document() {
503        let doc = vec!["fn f() { g(a[i], (b)); }"; 2000].join("\n");
504        let mut buf = Buffer::new(&doc).expect("fixture loads");
505        let mut b = Brackets::match_text(&buf.text());
506        let target = buf.point_to_offset(Point::new(1000, 8));
507        edit(&mut b, &mut buf, vec![EditOp::insert(target, "()")]);
508        assert_oracle(&b, &buf, "balanced pair at line 1000");
509    }
510
511    #[test]
512    fn structural_edit_after_many_sibling_blocks() {
513        // A newline at depth 0 after N closed sibling blocks — the seed reconcile
514        // walks nothing (enclosing is an O(log) descent), but the result must still
515        // be oracle-correct.
516        const BLOCKS: usize = 20;
517        let mut text = String::new();
518        for _ in 0..BLOCKS {
519            text.push_str("{[[[[[[[[[[]]]]]]]]]]}\n");
520        }
521        let tail = text.len() as u32;
522        text.push_str("tail");
523        let mut buf = Buffer::new(&text).expect("fixture loads");
524        let mut b = Brackets::match_text(&buf.text());
525        edit(&mut b, &mut buf, vec![EditOp::insert(tail, "\n")]);
526        assert_oracle(&b, &buf, "newline after N closed sibling blocks");
527    }
528
529    #[test]
530    fn structure_neutral_insert_at_document_start() {
531        let mut lines = vec!["alpha", "beta"]; // two bracket-free head rows
532        lines.extend(std::iter::repeat_n("x(y[z]) {w}", 1998));
533        let doc = lines.join("\n");
534        let mut buf = Buffer::new(&doc).expect("fixture loads");
535        let mut b = Brackets::match_text(&buf.text());
536        let region = edit(&mut b, &mut buf, vec![EditOp::insert(0, "q")]);
537        assert_eq!(region, 0..0, "a structure-neutral insert reconciles nothing");
538        assert_oracle(&b, &buf, "letter at offset 0");
539    }
540
541    #[test]
542    fn scattered_multicursor_typing_is_structure_neutral() {
543        // THE document-scale multi-cursor case: a plain-letter keystroke at N
544        // cursors spread top-to-bottom is per-edit O(log) (the delta-gap suffix does
545        // not move) and reconciles nothing.
546        let doc = vec!["fn f() { g(a[i], (b)); }"; 500].join("\n");
547        let mut buf = Buffer::new(&doc).expect("fixture loads");
548        let mut b = Brackets::match_text(&buf.text());
549        let inserts: Vec<EditOp> = (0..500)
550            .step_by(50)
551            .map(|row| EditOp::insert(buf.point_to_offset(Point::new(row, 0)), "x"))
552            .collect();
553        let region = edit(&mut b, &mut buf, inserts);
554        assert_eq!(region, 0..0, "scattered structure-neutral edits reconcile nothing");
555        assert_oracle(&b, &buf, "scattered multi-cursor letter inserts");
556    }
557
558    #[test]
559    fn unbalanced_opener_at_the_top() {
560        // An unbalanced opener changes every downstream depth — but depth is derived,
561        // so the tree just gains one bracket. Oracle-equal, and the opener is
562        // unmatched.
563        let doc = vec!["(x)"; 100].join("\n");
564        let mut buf = Buffer::new(&doc).expect("fixture loads");
565        let mut b = Brackets::match_text(&buf.text());
566        edit(&mut b, &mut buf, vec![EditOp::insert(0, "{")]);
567        assert_oracle(&b, &buf, "unbalanced opener at the top");
568        assert_eq!((bat(&b, 0).partner, bat(&b, 0).open), (None, true));
569    }
570
571    // ─────────────────────────────── edges ───────────────────────────────
572
573    #[test]
574    fn empty_document_first_insert_and_full_delete() {
575        let mut buf = Buffer::new("").expect("empty loads");
576        let mut b = Brackets::match_text(&buf.text());
577        // An empty patch is a no-op with an empty reconcile window.
578        assert_eq!(b.apply_edit(&Patch::new(), &buf), 0..0);
579        edit(&mut b, &mut buf, vec![EditOp::insert(0, "({\n[")]);
580        assert_oracle(&b, &buf, "first insert into an empty document");
581        let len = buf.len();
582        edit(&mut b, &mut buf, vec![EditOp::delete(0..len)]);
583        assert_oracle(&b, &buf, "delete everything");
584        assert!(b.all().is_empty());
585    }
586
587    #[test]
588    fn edit_at_eof_appends() {
589        let mut buf = Buffer::new("(a\n[b").expect("fixture loads");
590        let mut b = Brackets::match_text(&buf.text());
591        let len = buf.len();
592        edit(&mut b, &mut buf, vec![EditOp::insert(len, "])")]); // "(a\n[b])"
593        assert_oracle(&b, &buf, "append at EOF");
594        assert_eq!(bat(&b, 0).partner, Some(6)); // ( … )
595        assert_eq!(bat(&b, 3).partner, Some(5)); // [ … ]
596    }
597
598    #[test]
599    fn whole_document_replace() {
600        let mut buf = Buffer::new("(a)\n[b]\n{c}").expect("fixture loads");
601        let mut b = Brackets::match_text(&buf.text());
602        let len = buf.len();
603        edit(&mut b, &mut buf, vec![EditOp::new(0..len, "{new\n(doc)\n}")]);
604        assert_oracle(&b, &buf, "whole-document replace");
605        let len = buf.len();
606        edit(&mut b, &mut buf, vec![EditOp::new(0..len, "[]")]);
607        assert_oracle(&b, &buf, "whole-document replace, fewer lines");
608    }
609
610    #[test]
611    fn edit_ending_exactly_on_a_line_boundary() {
612        let mut buf = Buffer::new("(a)\n[b]\n{c}").expect("fixture loads");
613        let mut b = Brackets::match_text(&buf.text());
614        let target = buf.point_to_offset(Point::new(1, 0));
615        edit(&mut b, &mut buf, vec![EditOp::insert(target, "(q)\n")]);
616        assert_oracle(&b, &buf, "insert ending with a newline");
617        let a = buf.point_to_offset(Point::new(1, 0));
618        let e = buf.point_to_offset(Point::new(2, 0));
619        edit(&mut b, &mut buf, vec![EditOp::delete(a..e)]);
620        assert_oracle(&b, &buf, "delete ending at a line start");
621    }
622
623    #[test]
624    fn multi_edit_transaction_with_scattered_inserts() {
625        // Two scattered inserts in ONE apply(): the pair they form must resolve.
626        let mut buf = Buffer::new("aaa\nbbb\nccc\nddd\neee").expect("fixture loads");
627        let mut b = Brackets::match_text(&buf.text());
628        let p1 = buf.point_to_offset(Point::new(0, 1));
629        let p2 = buf.point_to_offset(Point::new(4, 1));
630        edit(&mut b, &mut buf, vec![EditOp::insert(p1, "("), EditOp::insert(p2, ")")]);
631        assert_oracle(&b, &buf, "scattered two-insert transaction");
632        assert_eq!(bat(&b, 1).partner, Some(18)); // "a(aa\n…\ne)ee"
633    }
634}