#![allow(dead_code)]
use std::ops::{ControlFlow, Range};
use crate::buffer::Buffer;
use crate::coords::Bias;
use crate::patch::Patch;
use crate::sum_tree::{Dimension, Item, SumTree, Summary};
#[cfg(any(test, debug_assertions))]
thread_local! {
pub(crate) static BRACKET_VIEW_CALLS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct BracketItem {
gap: u32,
ch: u8,
}
fn is_opener(c: u8) -> bool {
matches!(c, b'(' | b'[' | b'{')
}
fn is_closer(c: u8) -> bool {
matches!(c, b')' | b']' | b'}')
}
fn pairs(o: u8, c: u8) -> bool {
matches!((o, c), (b'(', b')') | (b'[', b']') | (b'{', b'}'))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Entry {
ch: u8,
off: u32,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct ShapeSummary {
span: u32,
count: u32,
pending: Vec<Entry>,
stack: Vec<Entry>,
}
impl Summary for ShapeSummary {
fn add_summary(&mut self, o: &Self) {
let sl = self.span; self.span += o.span;
self.count += o.count;
for c in &o.pending {
let c = Entry { ch: c.ch, off: c.off + sl };
match self.stack.last() {
Some(t) if pairs(t.ch, c.ch) => {
self.stack.pop();
}
Some(_) => {} None => self.pending.push(c),
}
}
self.stack.extend(o.stack.iter().map(|e| Entry { ch: e.ch, off: e.off + sl }));
}
}
impl Item for BracketItem {
type Summary = ShapeSummary;
fn summary(&self) -> ShapeSummary {
let e = Entry { ch: self.ch, off: self.gap };
let (pending, stack) = if is_opener(self.ch) { (vec![], vec![e]) } else { (vec![e], vec![]) };
ShapeSummary { span: self.gap, count: 1, pending, stack }
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
struct ByteDim(u32);
impl Dimension<ShapeSummary> for ByteDim {
fn add_summary(&mut self, s: &ShapeSummary) {
self.0 += s.span;
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
struct CountDim(u32);
impl Dimension<ShapeSummary> for CountDim {
fn add_summary(&mut self, s: &ShapeSummary) {
self.0 += s.count;
}
}
fn enclosing_openers(tree: &SumTree<BracketItem>, offset: u32) -> Vec<Entry> {
tree.summary_before(&ByteDim(offset)).stack
}
fn partner(tree: &SumTree<BracketItem>, offset: u32, ch: u8) -> Option<u32> {
if is_opener(ch) {
opener_partner(tree, offset, ch)
} else {
closer_partner(tree, offset, ch)
}
}
fn closer_partner(tree: &SumTree<BracketItem>, offset: u32, ch: u8) -> Option<u32> {
crate::perf::charge(1); match tree.summary_before(&ByteDim(offset)).stack.last() {
Some(top) if pairs(top.ch, ch) => Some(top.off),
_ => None,
}
}
fn opener_partner(tree: &SumTree<BracketItem>, offset: u32, ch: u8) -> Option<u32> {
crate::perf::charge(1); if tree.summary().stack.iter().any(|e| e.off == offset) {
return None;
}
let mut ls: Vec<u8> = Vec::new();
tree.try_suffix_summaries(&ByteDim(offset + 1), &mut |shape: &ShapeSummary, start: &ByteDim| {
for c in &shape.pending {
match ls.last() {
Some(&t) if pairs(t, c.ch) => {
ls.pop();
}
Some(_) => {} None => {
if pairs(ch, c.ch) {
return ControlFlow::Break(start.0 + c.off);
}
}
}
}
for e in &shape.stack {
ls.push(e.ch);
}
ControlFlow::Continue(())
})
}
use crate::bracket::Bracket;
pub(crate) fn tree_from_text(text: &str) -> SumTree<BracketItem> {
let mut items: Vec<BracketItem> = Vec::new();
let mut prev = 0u32;
for (i, b) in text.bytes().enumerate() {
if is_opener(b) || is_closer(b) {
let off = i as u32;
items.push(BracketItem { gap: off - prev, ch: b });
prev = off;
}
}
SumTree::from_items(items)
}
pub(crate) fn tree_from_text_with(
text: &str,
cfg: &crate::bracket::BracketConfig,
) -> SumTree<BracketItem> {
let mut items: Vec<BracketItem> = Vec::new();
let mut prev = 0u32;
for (off, b, skip) in crate::bracket::SkipContext::new(text.as_bytes(), cfg) {
if !skip && (is_opener(b) || is_closer(b)) {
items.push(BracketItem { gap: off - prev, ch: b });
prev = off;
}
}
SumTree::from_items(items)
}
fn item_char_at(tree: &SumTree<BracketItem>, offset: u32) -> Option<u8> {
let CountDim(k) = tree.measure_before::<ByteDim, CountDim>(&ByteDim(offset));
let (item, _c, ByteDim(start)) = tree.seek::<CountDim, ByteDim>(&CountDim(k))?;
(start + item.gap == offset).then_some(item.ch)
}
pub(crate) fn foldable_partner(tree: &SumTree<BracketItem>, offset: u32) -> Option<u32> {
let ch = item_char_at(tree, offset)?;
is_opener(ch).then(|| opener_partner(tree, offset, ch)).flatten()
}
fn bracket_view(tree: &SumTree<BracketItem>, offset: u32, ch: u8) -> Bracket {
#[cfg(any(test, debug_assertions))]
BRACKET_VIEW_CALLS.with(|c| c.set(c.get() + 1));
crate::perf::charge(1); let stack = tree.summary_before(&ByteDim(offset)).stack;
if is_opener(ch) {
Bracket { offset, open: true, depth: stack.len() as u32, partner: opener_partner(tree, offset, ch) }
} else if let Some(top) = stack.last().filter(|t| pairs(t.ch, ch)) {
Bracket { offset, open: false, depth: stack.len() as u32 - 1, partner: Some(top.off) }
} else {
Bracket { offset, open: false, depth: 0, partner: None }
}
}
pub(crate) fn at(tree: &SumTree<BracketItem>, offset: u32) -> Option<Bracket> {
item_char_at(tree, offset).map(|ch| bracket_view(tree, offset, ch))
}
pub(crate) fn in_range(
tree: &SumTree<BracketItem>,
start: u32,
end: u32,
) -> impl Iterator<Item = Bracket> + '_ {
let CountDim(k_lo) = tree.measure_before::<ByteDim, CountDim>(&ByteDim(start));
let CountDim(k_hi) = tree.measure_before::<ByteDim, CountDim>(&ByteDim(end));
(k_lo..k_hi).filter_map(move |k| {
let (item, _c, ByteDim(s)) = tree.seek::<CountDim, ByteDim>(&CountDim(k))?;
Some(bracket_view(tree, s + item.gap, item.ch))
})
}
pub(crate) fn derive_all(tree: &SumTree<BracketItem>) -> Vec<Bracket> {
let mut out: Vec<Bracket> = Vec::new();
let mut stack: Vec<(u32, u8, usize)> = Vec::new(); let mut off = 0u32;
for item in tree.item_refs() {
off += item.gap;
let ch = item.ch;
if is_opener(ch) {
let depth = stack.len() as u32;
stack.push((off, ch, out.len()));
out.push(Bracket { offset: off, open: true, depth, partner: None });
} else if let Some(&(ooff, _, oi)) = stack.last().filter(|(_, och, _)| pairs(*och, ch)) {
stack.pop();
out[oi].partner = Some(off);
out.push(Bracket { offset: off, open: false, depth: stack.len() as u32, partner: Some(ooff) });
} else {
out.push(Bracket { offset: off, open: false, depth: 0, partner: None });
}
}
out
}
pub(crate) fn active_pair(tree: &SumTree<BracketItem>, caret: u32) -> Option<(u32, u32)> {
let matched = |off: u32| at(tree, off).filter(|b| b.partner.is_some());
let b = caret
.checked_sub(1)
.and_then(matched)
.or_else(|| matched(caret))?;
Some((b.offset, b.partner.expect("filtered to matched")))
}
pub(crate) fn enclosing_pairs(tree: &SumTree<BracketItem>, caret: u32) -> Vec<(u32, u32)> {
let stack = tree.summary_before(&ByteDim(caret)).stack;
stack
.iter()
.rev()
.filter_map(|e| opener_partner(tree, e.off, e.ch).map(|c| (e.off, c)))
.collect()
}
pub(crate) fn enclosing_or_touching_pairs(tree: &SumTree<BracketItem>, caret: u32) -> Vec<(u32, u32)> {
let mut out = Vec::new();
if let Some(ch) = item_char_at(tree, caret).filter(|&c| is_opener(c)) {
if let Some(close) = opener_partner(tree, caret, ch) {
out.push((caret, close));
}
}
if let Some(prev) = caret.checked_sub(1) {
if let Some(ch) = item_char_at(tree, prev).filter(|&c| is_closer(c)) {
if let Some(open) = closer_partner(tree, prev, ch) {
out.push((open, prev));
}
}
}
out.extend(enclosing_pairs(tree, caret));
out
}
pub(crate) fn apply_edit(
tree: &SumTree<BracketItem>,
patch: &Patch,
buffer: &Buffer,
cfg: &crate::bracket::BracketConfig,
) -> (SumTree<BracketItem>, Range<u32>) {
let edits = patch.edits();
if edits.is_empty() {
return (tree.clone(), 0..0);
}
if !cfg.is_active() && edits.len() > 64 && is_structure_neutral(tree, patch, buffer) {
return (bulk_shift(tree, patch), 0..0);
}
let regions: Vec<(Range<u32>, u32)> = if cfg.is_active() {
line_aligned_regions(patch, buffer)
} else {
edits.iter().map(|e| (e.new.clone(), e.old.end - e.old.start)).collect()
};
let rs0 = regions[0].0.start;
let seed_lo = enclosing_openers(tree, rs0).first().map_or(rs0, |e| e.off);
let re = regions[regions.len() - 1].0.end;
let mut cur = tree.clone();
let mut structural = false;
for (new_range, old_len) in ®ions {
let rs = new_range.start;
let re_new = new_range.end;
let re_old = rs + old_len;
let delta = i64::from(re_new) - i64::from(re_old);
let k_rs = cur.summary_before(&ByteDim(rs)).count;
let k_re_old = cur.summary_before(&ByteDim(re_old)).count;
let before = cur.split_at(&CountDim(k_rs)).0;
let before_end = before.extent::<ByteDim>().0;
let removed = k_re_old - k_rs;
let mut region_items: Vec<BracketItem> = Vec::new();
let mut prev = before_end;
if re_new > rs {
let slice = buffer.slice(rs..re_new);
if cfg.is_active() {
for (i, byte, skip) in crate::bracket::SkipContext::new(slice.as_bytes(), cfg) {
if !skip && (is_opener(byte) || is_closer(byte)) {
let off = rs + i;
region_items.push(BracketItem { gap: off - prev, ch: byte });
prev = off;
}
}
} else {
for (i, b) in slice.bytes().enumerate() {
if is_opener(b) || is_closer(b) {
let off = rs + i as u32;
region_items.push(BracketItem { gap: off - prev, ch: b });
prev = off;
}
}
}
}
if removed > 0 || !region_items.is_empty() {
structural = true;
}
let suffix = cur.split_at(&CountDim(k_re_old)).1;
let mid = before.append(&SumTree::from_items(region_items));
let fixed_suffix = reanchor_suffix(&suffix, k_re_old, &cur, delta, prev);
cur = mid.append(&fixed_suffix);
}
(cur, if structural { seed_lo..re.saturating_add(1) } else { 0..0 })
}
fn line_aligned_regions(patch: &Patch, buffer: &Buffer) -> Vec<(Range<u32>, u32)> {
use crate::coords::Point;
let line_start = |off: u32| buffer.point_to_offset(Point::new(buffer.offset_to_point(off).row, 0));
let next_line_start = |off: u32| {
let row = buffer.offset_to_point(off).row;
if row + 1 < buffer.line_count() {
buffer.point_to_offset(Point::new(row + 1, 0))
} else {
buffer.len()
}
};
let mut merged: Vec<(u32, u32, i64)> = Vec::new();
for e in patch.edits() {
let ns = line_start(e.new.start);
let ne = next_line_start(e.new.end);
let d = i64::from(e.new.end - e.new.start) - i64::from(e.old.end - e.old.start);
match merged.last_mut() {
Some(last) if ns <= last.1 => {
last.1 = last.1.max(ne);
last.2 += d;
}
_ => merged.push((ns, ne, d)),
}
}
merged
.into_iter()
.map(|(ns, ne, d)| (ns..ne, (i64::from(ne - ns) - d).max(0) as u32))
.collect()
}
fn is_structure_neutral(tree: &SumTree<BracketItem>, patch: &Patch, buffer: &Buffer) -> bool {
patch.edits().iter().all(|e| {
let no_insert = e.new.start == e.new.end
|| buffer.slice(e.new.start..e.new.end).bytes().all(|c| !is_opener(c) && !is_closer(c));
let CountDim(lo) = tree.measure_before::<ByteDim, CountDim>(&ByteDim(e.old.start));
let CountDim(hi) = tree.measure_before::<ByteDim, CountDim>(&ByteDim(e.old.end));
no_insert && lo == hi
})
}
fn bulk_shift(tree: &SumTree<BracketItem>, patch: &Patch) -> SumTree<BracketItem> {
let items = tree.item_refs();
if items.is_empty() {
return tree.clone();
}
let mut off = 0u32;
let mut queries: Vec<(u32, Bias)> = Vec::with_capacity(items.len());
for it in &items {
off += it.gap;
queries.push((off, Bias::Right));
}
let mut mapped: Vec<u32> = Vec::new();
patch.map_many(&queries, &mut mapped);
let mut prev = 0u32;
let new_items: Vec<BracketItem> = mapped
.iter()
.zip(&items)
.map(|(&o, it)| {
debug_assert!(o >= prev, "structure-neutral shift keeps brackets ordered");
let gap = o - prev;
prev = o;
BracketItem { gap, ch: it.ch }
})
.collect();
SumTree::from_items(new_items)
}
fn reanchor_suffix(
suffix: &SumTree<BracketItem>,
k: u32,
cur: &SumTree<BracketItem>,
delta: i64,
last_off: u32,
) -> SumTree<BracketItem> {
if suffix.is_empty() {
return suffix.clone();
}
let (item, _c, ByteDim(s)) =
cur.seek::<CountDim, ByteDim>(&CountDim(k)).expect("suffix non-empty ⇒ a k-th bracket");
let first_old_off = s + item.gap;
let new_gap = (i64::from(first_old_off) + delta - i64::from(last_off)) as u32;
let fixed = BracketItem { gap: new_gap, ch: item.ch };
suffix.replace(CountDim(0)..CountDim(1), std::iter::once(fixed))
}
fn shape_of(brackets: &[(u32, u8)]) -> (Vec<Entry>, Vec<Entry>) {
let mut stack: Vec<Entry> = Vec::new();
let mut pending: Vec<Entry> = Vec::new();
for &(off, c) in brackets {
let e = Entry { ch: c, off };
if is_opener(c) {
stack.push(e);
} else {
match stack.last() {
Some(t) if pairs(t.ch, c) => {
stack.pop();
}
Some(_) => {}
None => pending.push(e),
}
}
}
(pending, stack)
}
#[cfg(test)]
fn scratch_partners(brackets: &[(u32, u8)]) -> std::collections::HashMap<u32, Option<u32>> {
let mut stack: Vec<(u32, u8)> = Vec::new();
let mut out: std::collections::HashMap<u32, Option<u32>> =
brackets.iter().map(|&(off, _)| (off, None)).collect();
for &(off, c) in brackets {
if is_opener(c) {
stack.push((off, c));
} else if let Some(&(ooff, och)) = stack.last() {
if pairs(och, c) {
stack.pop();
out.insert(off, Some(ooff));
out.insert(ooff, Some(off));
}
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sum_tree::SumTree;
fn build(runs: &[(u32, u8)]) -> (SumTree<BracketItem>, Vec<(u32, u8)>) {
let tree = SumTree::from_items(runs.iter().map(|&(gap, ch)| BracketItem { gap, ch }));
let mut off = 0;
let oracle = runs
.iter()
.map(|&(gap, ch)| {
off += gap;
(off, ch)
})
.collect();
(tree, oracle)
}
#[test]
fn shape_monoid_matches_the_scratch_stack_machine() {
let all = b"()[]{}";
let mut state = 0xB0A7u32;
let mut next = || {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state
};
for n in 0..600 {
let len = (n % 50) + (next() as usize % 60);
let runs: Vec<(u32, u8)> =
(0..len).map(|_| (1 + next() % 5, all[next() as usize % all.len()])).collect();
let (tree, oracle) = build(&runs);
let s = tree.summary().clone();
let (pending, stack) = shape_of(&oracle);
assert_eq!((&s.pending, &s.stack), (&pending, &stack), "len={len}");
assert_eq!(s.count, len as u32);
}
let (tree, _) = build(&[(3, b'('), (4, b'(')]);
assert_eq!(tree.summary().stack, vec![Entry { ch: b'(', off: 3 }, Entry { ch: b'(', off: 7 }]);
let (tree, _) = build(&[1, 1, 1, 1, 1, 1, 1, 1, 1].iter().zip(b"[[{(}})]]").map(|(&g, &c)| (g, c)).collect::<Vec<_>>());
assert!(tree.summary().pending.is_empty());
assert_eq!(tree.summary().stack.iter().map(|e| e.ch).collect::<Vec<_>>(), b"[[{");
}
#[test]
fn enclosing_openers_matches_the_scratch_stack() {
let all = b"()[]{}";
let mut state = 0x515Eu32;
let mut next = || {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state
};
for _ in 0..200 {
let len = 1 + next() as usize % 80;
let runs: Vec<(u32, u8)> =
(0..len).map(|_| (1 + next() % 4, all[next() as usize % all.len()])).collect();
let (tree, oracle) = build(&runs);
let top = oracle.last().map_or(0, |&(o, _)| o) + 3;
for x in 0..=top {
let before: Vec<(u32, u8)> = oracle.iter().copied().filter(|&(o, _)| o < x).collect();
let (_, want_stack) = shape_of(&before);
assert_eq!(enclosing_openers(&tree, x), want_stack, "offset {x}");
}
}
}
#[test]
fn partner_matches_the_scratch_stack_machine() {
let all = b"()[]{}";
let mut state = 0x9A27u32;
let mut next = || {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state
};
for _ in 0..300 {
let len = 1 + next() as usize % 90;
let runs: Vec<(u32, u8)> =
(0..len).map(|_| (1 + next() % 4, all[next() as usize % all.len()])).collect();
let (tree, oracle) = build(&runs);
let want = scratch_partners(&oracle);
for &(off, ch) in &oracle {
assert_eq!(partner(&tree, off, ch), want[&off], "bracket {ch:?} at {off}");
}
}
let (tree, _) = build(&[(0, b'('), (2, b']'), (2, b')')]); assert_eq!(partner(&tree, 0, b'('), Some(4));
assert_eq!(partner(&tree, 4, b')'), Some(0));
assert_eq!(partner(&tree, 2, b']'), None);
let (tree, _) =
build(&[(0, b'('), (2, b'['), (2, b']'), (2, b'{'), (2, b'}'), (2, b')')]);
assert_eq!(partner(&tree, 0, b'('), Some(10));
assert_eq!(partner(&tree, 2, b'['), Some(4));
assert_eq!(partner(&tree, 6, b'{'), Some(8));
let (tree, _) = build(&[(0, b'{'), (1, b']')]);
assert_eq!(partner(&tree, 0, b'{'), None);
assert_eq!(partner(&tree, 1, b']'), None);
}
fn random_text(next: &mut impl FnMut() -> u32) -> String {
const POOL: &[u8] = b"()[]{}()[]{}abc \n";
let len = 1 + next() as usize % 160;
(0..len).map(|_| POOL[next() as usize % POOL.len()] as char).collect()
}
#[test]
fn derivations_match_the_vec_engine() {
let mut state = 0x1D01u32;
let mut next = || {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state
};
for _ in 0..400 {
let text = random_text(&mut next);
let real = crate::bracket::Brackets::match_text(&text);
let tree = tree_from_text(&text);
assert_eq!(derive_all(&tree), real.all(), "derive_all: {text:?}");
for b in real.all() {
let ch = text.as_bytes()[b.offset as usize];
assert_eq!(bracket_view(&tree, b.offset, ch), b, "bracket_view @{}: {text:?}", b.offset);
assert_eq!(at(&tree, b.offset), Some(b), "at @{}: {text:?}", b.offset);
assert_eq!(item_char_at(&tree, b.offset), Some(ch));
}
for off in 0..text.len() as u32 {
if !is_opener(text.as_bytes()[off as usize]) && !is_closer(text.as_bytes()[off as usize]) {
assert_eq!(at(&tree, off), None, "non-bracket @{off}: {text:?}");
assert_eq!(item_char_at(&tree, off), None);
}
}
}
}
#[test]
fn queries_match_the_vec_engine() {
let mut state = 0x7A11u32;
let mut next = || {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state
};
for _ in 0..300 {
let text = random_text(&mut next);
let real = crate::bracket::Brackets::match_text(&text);
let tree = tree_from_text(&text);
for caret in 0..=text.len() as u32 {
assert_eq!(active_pair(&tree, caret), real.active_pair(caret), "active_pair @{caret}: {text:?}");
assert_eq!(
enclosing_pairs(&tree, caret).first().copied(),
real.enclosing_pair(caret),
"enclosing_pair @{caret}: {text:?}"
);
let mut got = enclosing_or_touching_pairs(&tree, caret);
got.sort_unstable();
let mut want = real.enclosing_or_touching(caret);
want.sort_unstable();
assert_eq!(got, want, "enclosing_or_touching @{caret}: {text:?}");
let end = (caret + 2).min(text.len() as u32);
let got_range = enclosing_pairs(&tree, caret).into_iter().find(|&(_, c)| end <= c);
assert_eq!(
got_range,
real.enclosing_pair_of_range(caret, end),
"enclosing_pair_of_range {caret}..{end}: {text:?}"
);
}
}
}
}