use std::ops::Range;
use crate::bracket_tree::{self, BracketItem};
use crate::buffer::Buffer;
use crate::patch::Patch;
use crate::sum_tree::SumTree;
#[cfg(any(test, debug_assertions))]
thread_local! {
pub(crate) static ENCLOSING_WALKS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
}
pub(crate) fn is_bracket_byte(c: u8) -> bool {
matches!(c, b'(' | b')' | b'[' | b']' | b'{' | b'}')
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct BracketConfig {
pub line_comments: Vec<String>,
pub string_delims: Vec<u8>,
pub char_delim: Option<u8>,
}
impl BracketConfig {
#[must_use]
pub fn is_active(&self) -> bool {
!self.line_comments.is_empty() || !self.string_delims.is_empty() || self.char_delim.is_some()
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Skip {
Code,
LineComment,
Str(u8),
Char(u8),
}
pub(crate) struct SkipContext<'a> {
bytes: &'a [u8],
i: usize,
cfg: &'a BracketConfig,
state: Skip,
escaped: bool,
}
impl<'a> SkipContext<'a> {
pub(crate) fn new(bytes: &'a [u8], cfg: &'a BracketConfig) -> Self {
Self { bytes, i: 0, cfg, state: Skip::Code, escaped: false }
}
fn step(&mut self, b: u8) -> bool {
if b == b'\n' {
let was_skip = self.state != Skip::Code;
self.state = Skip::Code;
self.escaped = false;
return was_skip; }
match self.state {
Skip::LineComment => true,
Skip::Str(delim) | Skip::Char(delim) => {
if self.escaped {
self.escaped = false;
} else if b == b'\\' {
self.escaped = true;
} else if b == delim {
self.state = Skip::Code;
}
true
}
Skip::Code => {
let opens_comment = self.cfg.line_comments.iter().any(|m| {
let mb = m.as_bytes();
mb.first() == Some(&b) && self.bytes[self.i..].starts_with(mb)
});
if opens_comment {
self.state = Skip::LineComment;
return true;
}
if self.cfg.string_delims.contains(&b) {
self.state = Skip::Str(b);
return true;
}
if Some(b) == self.cfg.char_delim {
self.state = Skip::Char(b);
return true;
}
false
}
}
}
}
impl Iterator for SkipContext<'_> {
type Item = (u32, u8, bool);
fn next(&mut self) -> Option<Self::Item> {
let b = *self.bytes.get(self.i)?;
let off = self.i as u32;
let skip = self.step(b);
self.i += 1;
Some((off, b, skip))
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Bracket {
pub offset: u32,
pub open: bool,
pub depth: u32,
pub partner: Option<u32>,
}
#[derive(Clone, Debug, Default)]
pub struct Brackets {
tree: SumTree<BracketItem>,
}
impl Brackets {
#[must_use]
pub fn match_text(text: &str) -> Self {
Self { tree: bracket_tree::tree_from_text(text) }
}
#[must_use]
pub fn match_text_with(text: &str, cfg: &BracketConfig) -> Self {
if !cfg.is_active() {
return Self::match_text(text);
}
Self { tree: bracket_tree::tree_from_text_with(text, cfg) }
}
#[must_use]
pub fn all(&self) -> Vec<Bracket> {
bracket_tree::derive_all(&self.tree)
}
#[must_use]
pub fn at(&self, offset: u32) -> Option<Bracket> {
bracket_tree::at(&self.tree, offset)
}
#[must_use]
pub fn foldable_partner(&self, offset: u32) -> Option<u32> {
bracket_tree::foldable_partner(&self.tree, offset)
}
#[must_use]
pub fn in_range(&self, range: Range<u32>) -> Vec<Bracket> {
self.in_range_iter(range).collect()
}
pub fn in_range_iter(&self, range: Range<u32>) -> impl Iterator<Item = Bracket> + '_ {
bracket_tree::in_range(&self.tree, range.start, range.end)
}
#[must_use]
pub fn active_pair(&self, caret: u32) -> Option<(u32, u32)> {
bracket_tree::active_pair(&self.tree, caret)
}
#[must_use]
pub fn enclosing_pair(&self, caret: u32) -> Option<(u32, u32)> {
bracket_tree::enclosing_pairs(&self.tree, caret).into_iter().next()
}
#[must_use]
pub fn innermost_enclosing_where(&self, caret: u32, pred: impl Fn(u32, u32) -> bool) -> Option<u32> {
bracket_tree::enclosing_pairs(&self.tree, caret)
.into_iter()
.find(|&(o, c)| pred(o, c))
.map(|(o, _)| o)
}
#[must_use]
pub fn enclosing_pair_of_range(&self, start: u32, end: u32) -> Option<(u32, u32)> {
bracket_tree::enclosing_pairs(&self.tree, start).into_iter().find(|&(_, close)| end <= close)
}
#[must_use]
pub fn enclosing_or_touching(&self, caret: u32) -> Vec<(u32, u32)> {
#[cfg(any(test, debug_assertions))]
ENCLOSING_WALKS.with(|c| c.set(c.get() + 1));
bracket_tree::enclosing_or_touching_pairs(&self.tree, caret)
}
pub fn apply_edit(&mut self, patch: &Patch, buffer: &Buffer, cfg: &BracketConfig) -> Range<u32> {
let (tree, region) = bracket_tree::apply_edit(&self.tree, patch, buffer, cfg);
self.tree = tree;
region
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::coords::Point;
use crate::transaction::{apply, EditOp};
fn bat(b: &Brackets, o: u32) -> Bracket {
b.at(o).expect("a bracket at that offset")
}
fn offs(b: &Brackets) -> Vec<u32> {
b.all().iter().map(|x| x.offset).collect()
}
fn cfg() -> BracketConfig {
BracketConfig {
line_comments: vec!["//".into()],
string_delims: vec![b'"'],
char_delim: None,
}
}
#[test]
fn inactive_config_is_identical_to_plain_match() {
let text = "(a)//(b)\n\"(\"[]";
assert_eq!(
Brackets::match_text_with(text, &BracketConfig::default()).all(),
Brackets::match_text(text).all(),
);
}
#[test]
fn skips_brackets_in_line_comments_and_resets_at_newline() {
let b = Brackets::match_text_with("(a) // (b)\n(c)", &cfg());
assert_eq!(offs(&b), vec![0, 2, 11, 13], "the comment's ( ) at 7,9 are skipped");
}
#[test]
fn skips_brackets_in_strings() {
let b = Brackets::match_text_with("\"(\"(", &cfg());
assert_eq!(offs(&b), vec![3]);
}
#[test]
fn precedence_is_positional_not_a_fixed_layer_order() {
let b = Brackets::match_text_with("\"//\"()", &cfg());
assert_eq!(offs(&b), vec![4, 5]);
}
#[test]
fn a_backslash_escapes_the_closing_quote() {
let b = Brackets::match_text_with("\"\\\")\"(", &cfg());
assert_eq!(offs(&b), vec![5]);
}
#[test]
fn char_delim_is_opt_in() {
let c = BracketConfig { char_delim: Some(b'\''), ..Default::default() };
assert_eq!(offs(&Brackets::match_text_with("'(')", &c)), vec![3]);
assert_eq!(offs(&Brackets::match_text_with("'(')", &BracketConfig::default())), vec![1, 3]);
}
#[test]
fn matches_nested_pairs_with_depth() {
let b = Brackets::match_text("(a[b]{c})"); assert_eq!((bat(&b, 0).partner, bat(&b, 0).depth), (Some(8), 0));
assert_eq!((bat(&b, 2).partner, bat(&b, 2).depth), (Some(4), 1));
assert_eq!((bat(&b, 5).partner, bat(&b, 5).depth), (Some(7), 1));
assert_eq!((bat(&b, 8).partner, bat(&b, 8).depth), (Some(0), 0));
}
#[test]
fn flags_unmatched_brackets() {
let b = Brackets::match_text("(]"); assert_eq!(bat(&b, 0).partner, None);
assert_eq!(bat(&b, 1).partner, None);
}
#[test]
fn active_pair_prefers_the_bracket_left_of_the_caret() {
let b = Brackets::match_text("(){}"); assert_eq!(b.active_pair(0), Some((0, 1))); assert_eq!(b.active_pair(1), Some((0, 1))); assert_eq!(b.active_pair(2), Some((1, 0))); assert_eq!(b.active_pair(3), Some((2, 3))); assert_eq!(b.active_pair(4), Some((3, 2))); assert_eq!(Brackets::match_text("x").active_pair(1), None); }
#[test]
fn enclosing_pair_is_the_innermost_containing_the_caret() {
let b = Brackets::match_text("(a[b]{c})"); assert_eq!(b.enclosing_pair(1), Some((0, 8))); assert_eq!(b.enclosing_pair(3), Some((2, 4))); assert_eq!(b.enclosing_pair(6), Some((5, 7))); assert_eq!(b.enclosing_pair(0), None); assert_eq!(b.enclosing_pair(9), None); }
#[test]
fn enclosing_walk_matches_a_brute_scan_with_siblings() {
let b = Brackets::match_text("([1][2]{a(b)c}[3])xy");
let brute_enclosing = |caret: u32| {
b.all()
.iter()
.filter_map(|br| br.partner.map(|p| (br.offset, p)).filter(|_| br.open))
.filter(|&(o, c)| o < caret && caret <= c)
.min_by_key(|&(o, c)| c - o)
};
for caret in 0..=20 {
assert_eq!(b.enclosing_pair(caret), brute_enclosing(caret), "enclosing_pair({caret})");
}
assert_eq!(b.enclosing_pair(10), Some((9, 11)));
assert_eq!(b.enclosing_pair(14), Some((0, 17)));
let mut touch = b.enclosing_or_touching(9); touch.sort_unstable();
assert!(touch.contains(&(9, 11)), "the pair opening at the caret is touched");
assert!(touch.contains(&(7, 13)) && touch.contains(&(0, 17)), "and its enclosers");
let after_close = b.enclosing_or_touching(12); assert!(after_close.contains(&(9, 11)), "the pair closing at caret-1 is touched");
assert_eq!(b.enclosing_pair_of_range(10, 11), Some((9, 11))); assert_eq!(b.enclosing_pair_of_range(9, 12), Some((7, 13))); }
#[test]
fn active_pair_skips_unmatched_brackets() {
let b = Brackets::match_text("(]"); assert_eq!(b.active_pair(1), None); assert_eq!(b.active_pair(2), None);
}
#[test]
fn quotes_are_not_brackets() {
let b = Brackets::match_text("\"()\""); assert_eq!(b.all().len(), 2);
assert_eq!(bat(&b, 1).partner, Some(2));
}
#[test]
fn in_range_boundaries() {
let b = Brackets::match_text("(a[b]{c})"); let offsets = |s: &[Bracket]| s.iter().map(|x| x.offset).collect::<Vec<_>>();
assert!(b.in_range(0..0).is_empty()); assert!(b.in_range(3..3).is_empty());
assert_eq!(b.in_range(0..9), b.all()); assert_eq!(b.in_range(0..u32::MAX), b.all());
assert_eq!(offsets(&b.in_range(2..5)), vec![2, 4]); assert_eq!(offsets(&b.in_range(2..6)), vec![2, 4, 5]); assert_eq!(offsets(&b.in_range(8..9)), vec![8]); assert_eq!(offsets(&b.in_range(1..2)), Vec::<u32>::new()); #[allow(clippy::reversed_empty_ranges)]
let inverted = b.in_range(5..2);
assert!(inverted.is_empty()); }
fn assert_oracle(b: &Brackets, buf: &Buffer, ctx: &str) {
let oracle = Brackets::match_text(&buf.text());
assert_eq!(b.all(), oracle.all(), "brackets diverged from a scratch rebuild: {ctx}");
}
fn edit(b: &mut Brackets, buf: &mut Buffer, ops: Vec<EditOp>) -> Range<u32> {
let committed = apply(buf, ops).expect("test edits are disjoint and in-bounds");
b.apply_edit(committed.patch(), buf, &BracketConfig::default())
}
#[test]
fn incremental_matches_scratch_after_random_edits() {
let seed: u64 = 0x5EED_0BAD_F00D_2026;
let mut s = seed;
let mut rng = move || {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
s
};
let mut buf = Buffer::new(
"fn main() {\n let v = vec![1, (2), [3]];\n if (a[i]) { b(c[d]); }\n}\n",
)
.expect("fixture loads");
let mut b = Brackets::match_text(&buf.text());
const POOL: &[u8] = b"(){}[](){}[]\n\nab ";
for i in 0..500 {
let len = buf.len();
let kind = rng() % 12;
let ops = if kind < 5 {
let at = (rng() % (u64::from(len) + 1)) as u32;
let n = 1 + (rng() % 4) as usize;
let text: String =
(0..n).map(|_| POOL[(rng() % POOL.len() as u64) as usize] as char).collect();
vec![EditOp::insert(at, text)]
} else if kind < 7 {
let a = (rng() % (u64::from(len) + 1)) as u32;
let e = (a + 1 + (rng() % 12) as u32).min(len);
vec![EditOp::delete(a..e)]
} else if kind < 9 {
let a = (rng() % (u64::from(len) + 1)) as u32;
let e = (a + (rng() % 6) as u32).min(len);
let n = 1 + (rng() % 5) as usize;
let text: String =
(0..n).map(|_| POOL[(rng() % POOL.len() as u64) as usize] as char).collect();
vec![EditOp::new(a..e, text)]
} else if kind == 9 {
let a = (rng() % (u64::from(len) / 2 + 1)) as u32;
let c = (len / 2 + (rng() % (u64::from(len) / 2 + 1)) as u32).min(len);
vec![EditOp::insert(a, "("), EditOp::insert(c, ")")]
} else {
let n = 2 + (rng() % 3) as usize; let mut offs: Vec<u32> =
(0..n).map(|_| (rng() % (u64::from(len) + 1)) as u32).collect();
offs.sort_unstable();
offs.dedup();
offs.into_iter().map(|o| EditOp::insert(o, "z")).collect()
};
let committed = apply(&mut buf, ops.clone()).expect("generated ops are disjoint");
b.apply_edit(committed.patch(), &buf, &BracketConfig::default());
let ctx = format!("edit {i} (seed {seed:#x}): {ops:?}");
assert_oracle(&b, &buf, &ctx);
}
}
#[test]
fn incremental_matches_scratch_with_comment_awareness() {
let cfg = BracketConfig {
line_comments: vec!["//".into()],
string_delims: vec![b'"'],
char_delim: None,
};
let seed: u64 = 0xC0FF_EE00_2026_1234;
let mut s = seed;
let mut rng = move || {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
s
};
let mut buf = Buffer::new("fn f() { // (a)\n let s = \"([{\";\n g([1]);\n}\n")
.expect("fixture loads");
let mut b = Brackets::match_text_with(&buf.text(), &cfg);
const POOL: &[u8] = b"(){}[]//\"\"\\\n ab";
for i in 0..600 {
let len = buf.len();
let kind = rng() % 12;
let ops = if kind < 5 {
let at = (rng() % (u64::from(len) + 1)) as u32;
let n = 1 + (rng() % 4) as usize;
let text: String =
(0..n).map(|_| POOL[(rng() % POOL.len() as u64) as usize] as char).collect();
vec![EditOp::insert(at, text)]
} else if kind < 7 {
let a = (rng() % (u64::from(len) + 1)) as u32;
let e = (a + 1 + (rng() % 10) as u32).min(len);
vec![EditOp::delete(a..e)]
} else if kind < 9 {
let a = (rng() % (u64::from(len) + 1)) as u32;
let e = (a + (rng() % 6) as u32).min(len);
let n = 1 + (rng() % 5) as usize;
let text: String =
(0..n).map(|_| POOL[(rng() % POOL.len() as u64) as usize] as char).collect();
vec![EditOp::new(a..e, text)]
} else {
let n = 2 + (rng() % 3) as usize;
let mut offs: Vec<u32> =
(0..n).map(|_| (rng() % (u64::from(len) + 1)) as u32).collect();
offs.sort_unstable();
offs.dedup();
offs.into_iter()
.map(|o| {
let byte = POOL[(rng() % POOL.len() as u64) as usize];
EditOp::insert(o, (byte as char).to_string())
})
.collect()
};
let committed = apply(&mut buf, ops.clone()).expect("generated ops are disjoint");
b.apply_edit(committed.patch(), &buf, &cfg);
let oracle = Brackets::match_text_with(&buf.text(), &cfg);
assert_eq!(
b.all(),
oracle.all(),
"aware brackets diverged from a scratch rebuild at edit {i} (seed {seed:#x}): {ops:?}",
);
}
}
#[test]
fn prefix_opener_repoints_into_region() {
let mut buf = Buffer::new("{\nx\n}").expect("fixture loads");
let mut b = Brackets::match_text(&buf.text());
edit(&mut b, &mut buf, vec![EditOp::new(2..3, "}")]); assert_oracle(&b, &buf, "line 1 x → }");
assert_eq!(bat(&b, 0).partner, Some(2));
assert_eq!(bat(&b, 2).partner, Some(0));
assert_eq!((bat(&b, 4).partner, bat(&b, 4).depth), (None, 0));
}
#[test]
fn converged_shape_different_identity() {
let mut buf = Buffer::new("{\nx\n}").expect("fixture loads");
let mut b = Brackets::match_text(&buf.text());
edit(&mut b, &mut buf, vec![EditOp::new(2..3, "}{")]); assert_oracle(&b, &buf, "line 1 x → }{");
assert_eq!(bat(&b, 0).partner, Some(2));
assert_eq!(bat(&b, 3).partner, Some(5));
assert_eq!(bat(&b, 5).partner, Some(3));
}
#[test]
fn converged_shape_crosses_into_the_suffix() {
let mut buf = Buffer::new("{\nx\nq\n}").expect("fixture loads");
let mut b = Brackets::match_text(&buf.text());
edit(&mut b, &mut buf, vec![EditOp::new(2..3, "}{")]); assert_oracle(&b, &buf, "line 1 x → }{ with a clean row before the closer");
assert_eq!(bat(&b, 0).partner, Some(2));
assert_eq!(bat(&b, 3).partner, Some(7));
assert_eq!(bat(&b, 7).partner, Some(3));
}
#[test]
fn crossing_repairs_mixed_seed_and_region_entries() {
let mut buf = Buffer::new("{\n(\nb\n)}").expect("fixture loads");
let mut b = Brackets::match_text(&buf.text());
edit(&mut b, &mut buf, vec![EditOp::new(2..3, ")(")]); assert_oracle(&b, &buf, "mixed carried stack");
assert_eq!((bat(&b, 2).partner, bat(&b, 2).depth), (None, 0)); assert_eq!(bat(&b, 3).partner, Some(7)); assert_eq!(bat(&b, 7).partner, Some(3));
assert_eq!(bat(&b, 0).partner, Some(8)); assert_eq!(bat(&b, 8).partner, Some(0));
}
#[test]
fn pair_spanning_edit_shifts_the_closer() {
let mut buf = Buffer::new("{\naaa\nzzz\n}").expect("fixture loads");
let mut b = Brackets::match_text(&buf.text());
edit(&mut b, &mut buf, vec![EditOp::new(2..5, "aaaaa")]); assert_oracle(&b, &buf, "interior growth inside a multi-line pair");
assert_eq!(bat(&b, 0).partner, Some(12)); assert_eq!(bat(&b, 12).partner, Some(0)); }
#[test]
fn seed_leftover_cleared_to_none() {
let mut buf = Buffer::new("{\n}").expect("fixture loads");
let mut b = Brackets::match_text(&buf.text());
edit(&mut b, &mut buf, vec![EditOp::delete(2..3)]); assert_oracle(&b, &buf, "closer deleted");
assert_eq!(b.all().len(), 1);
assert_eq!((bat(&b, 0).partner, bat(&b, 0).open), (None, true));
}
#[test]
fn keystroke_deep_in_2000_lines() {
let doc = vec!["fn f() { g(a[i], (b)); }"; 2000].join("\n");
let mut buf = Buffer::new(&doc).expect("fixture loads");
let mut b = Brackets::match_text(&buf.text());
let target = buf.point_to_offset(Point::new(1000, 3));
edit(&mut b, &mut buf, vec![EditOp::insert(target, "x")]);
assert_oracle(&b, &buf, "keystroke at line 1000");
}
#[test]
fn balanced_pair_insert_deep_in_a_document() {
let doc = vec!["fn f() { g(a[i], (b)); }"; 2000].join("\n");
let mut buf = Buffer::new(&doc).expect("fixture loads");
let mut b = Brackets::match_text(&buf.text());
let target = buf.point_to_offset(Point::new(1000, 8));
edit(&mut b, &mut buf, vec![EditOp::insert(target, "()")]);
assert_oracle(&b, &buf, "balanced pair at line 1000");
}
#[test]
fn structural_edit_after_many_sibling_blocks() {
const BLOCKS: usize = 20;
let mut text = String::new();
for _ in 0..BLOCKS {
text.push_str("{[[[[[[[[[[]]]]]]]]]]}\n");
}
let tail = text.len() as u32;
text.push_str("tail");
let mut buf = Buffer::new(&text).expect("fixture loads");
let mut b = Brackets::match_text(&buf.text());
edit(&mut b, &mut buf, vec![EditOp::insert(tail, "\n")]);
assert_oracle(&b, &buf, "newline after N closed sibling blocks");
}
#[test]
fn structure_neutral_insert_at_document_start() {
let mut lines = vec!["alpha", "beta"]; lines.extend(std::iter::repeat_n("x(y[z]) {w}", 1998));
let doc = lines.join("\n");
let mut buf = Buffer::new(&doc).expect("fixture loads");
let mut b = Brackets::match_text(&buf.text());
let region = edit(&mut b, &mut buf, vec![EditOp::insert(0, "q")]);
assert_eq!(region, 0..0, "a structure-neutral insert reconciles nothing");
assert_oracle(&b, &buf, "letter at offset 0");
}
#[test]
fn scattered_multicursor_typing_is_structure_neutral() {
let doc = vec!["fn f() { g(a[i], (b)); }"; 500].join("\n");
let mut buf = Buffer::new(&doc).expect("fixture loads");
let mut b = Brackets::match_text(&buf.text());
let inserts: Vec<EditOp> = (0..500)
.step_by(50)
.map(|row| EditOp::insert(buf.point_to_offset(Point::new(row, 0)), "x"))
.collect();
let region = edit(&mut b, &mut buf, inserts);
assert_eq!(region, 0..0, "scattered structure-neutral edits reconcile nothing");
assert_oracle(&b, &buf, "scattered multi-cursor letter inserts");
}
#[test]
fn unbalanced_opener_at_the_top() {
let doc = vec!["(x)"; 100].join("\n");
let mut buf = Buffer::new(&doc).expect("fixture loads");
let mut b = Brackets::match_text(&buf.text());
edit(&mut b, &mut buf, vec![EditOp::insert(0, "{")]);
assert_oracle(&b, &buf, "unbalanced opener at the top");
assert_eq!((bat(&b, 0).partner, bat(&b, 0).open), (None, true));
}
#[test]
fn empty_document_first_insert_and_full_delete() {
let mut buf = Buffer::new("").expect("empty loads");
let mut b = Brackets::match_text(&buf.text());
assert_eq!(b.apply_edit(&Patch::new(), &buf, &BracketConfig::default()), 0..0);
edit(&mut b, &mut buf, vec![EditOp::insert(0, "({\n[")]);
assert_oracle(&b, &buf, "first insert into an empty document");
let len = buf.len();
edit(&mut b, &mut buf, vec![EditOp::delete(0..len)]);
assert_oracle(&b, &buf, "delete everything");
assert!(b.all().is_empty());
}
#[test]
fn edit_at_eof_appends() {
let mut buf = Buffer::new("(a\n[b").expect("fixture loads");
let mut b = Brackets::match_text(&buf.text());
let len = buf.len();
edit(&mut b, &mut buf, vec![EditOp::insert(len, "])")]); assert_oracle(&b, &buf, "append at EOF");
assert_eq!(bat(&b, 0).partner, Some(6)); assert_eq!(bat(&b, 3).partner, Some(5)); }
#[test]
fn whole_document_replace() {
let mut buf = Buffer::new("(a)\n[b]\n{c}").expect("fixture loads");
let mut b = Brackets::match_text(&buf.text());
let len = buf.len();
edit(&mut b, &mut buf, vec![EditOp::new(0..len, "{new\n(doc)\n}")]);
assert_oracle(&b, &buf, "whole-document replace");
let len = buf.len();
edit(&mut b, &mut buf, vec![EditOp::new(0..len, "[]")]);
assert_oracle(&b, &buf, "whole-document replace, fewer lines");
}
#[test]
fn edit_ending_exactly_on_a_line_boundary() {
let mut buf = Buffer::new("(a)\n[b]\n{c}").expect("fixture loads");
let mut b = Brackets::match_text(&buf.text());
let target = buf.point_to_offset(Point::new(1, 0));
edit(&mut b, &mut buf, vec![EditOp::insert(target, "(q)\n")]);
assert_oracle(&b, &buf, "insert ending with a newline");
let a = buf.point_to_offset(Point::new(1, 0));
let e = buf.point_to_offset(Point::new(2, 0));
edit(&mut b, &mut buf, vec![EditOp::delete(a..e)]);
assert_oracle(&b, &buf, "delete ending at a line start");
}
#[test]
fn multi_edit_transaction_with_scattered_inserts() {
let mut buf = Buffer::new("aaa\nbbb\nccc\nddd\neee").expect("fixture loads");
let mut b = Brackets::match_text(&buf.text());
let p1 = buf.point_to_offset(Point::new(0, 1));
let p2 = buf.point_to_offset(Point::new(4, 1));
edit(&mut b, &mut buf, vec![EditOp::insert(p1, "("), EditOp::insert(p2, ")")]);
assert_oracle(&b, &buf, "scattered two-insert transaction");
assert_eq!(bat(&b, 1).partner, Some(18)); }
}