use crate::bracket::Brackets;
use crate::buffer::Buffer;
use crate::coords::{Bias, Point};
use crate::display_map::{BufferRow, DisplayRow};
use crate::offset_set::OffsetSet;
use crate::patch::Patch;
use crate::sum_tree::{Dimension, Item, SumTree, Summary};
#[cfg(any(test, debug_assertions))]
thread_local! {
pub(crate) static FOLD_BUILDS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
}
#[cfg(any(test, debug_assertions))]
thread_local! {
pub(crate) static RECONCILE_SCANS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
}
#[derive(Clone, Default, Debug)]
pub struct FoldSet {
offsets: OffsetSet,
generation: u64,
}
impl FoldSet {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.offsets.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.offsets.len()
}
#[must_use]
pub fn first_at_or_after(&self, offset: u32) -> Option<u32> {
self.offsets.first_at_or_after(offset)
}
#[must_use]
pub fn generation(&self) -> u64 {
self.generation
}
fn bump(&mut self) {
self.generation = self.generation.wrapping_add(1);
}
pub fn clear(&mut self) {
self.offsets = OffsetSet::new();
self.bump();
}
pub fn fold(&mut self, open: u32) -> bool {
if self.offsets.insert(open) {
self.bump();
true
} else {
false
}
}
pub fn unfold(&mut self, open: u32) -> bool {
if self.offsets.remove(open) {
self.bump();
true
} else {
false
}
}
pub fn fold_all(&mut self, opens: impl IntoIterator<Item = u32>) {
for open in opens {
self.offsets.insert(open); }
self.bump();
}
pub fn unfold_all(&mut self, opens: &[u32]) {
if opens.is_empty() {
return;
}
self.offsets.remove_all(opens);
self.bump();
}
#[must_use]
pub fn is_folded(&self, open: u32) -> bool {
self.offsets.contains(open)
}
pub fn toggle(&mut self, open: u32) -> bool {
if self.is_folded(open) {
self.unfold(open)
} else {
self.fold(open)
}
}
pub fn apply_patch(&mut self, patch: &Patch) {
self.offsets.apply_patch(patch);
}
pub fn reconcile(&mut self, is_foldable_opener: impl Fn(u32) -> bool) {
#[cfg(any(test, debug_assertions))]
RECONCILE_SCANS.with(|c| c.set(c.get() + 1));
crate::perf::charge(self.offsets.len() as u64); let drop: Vec<u32> =
self.offsets.offsets().into_iter().filter(|&o| !is_foldable_opener(o)).collect();
self.offsets.remove_all(&drop);
self.bump();
}
#[must_use]
pub fn openers_in(&self, range: core::ops::Range<u32>) -> Vec<u32> {
self.offsets.in_range(range.start, range.end)
}
pub fn reconcile_only(&mut self, candidates: &[u32], is_foldable_opener: impl Fn(u32) -> bool) {
let drop: Vec<u32> = candidates.iter().copied().filter(|&o| !is_foldable_opener(o)).collect();
if drop.is_empty() {
return; }
#[cfg(any(test, debug_assertions))]
RECONCILE_SCANS.with(|c| c.set(c.get() + 1));
crate::perf::charge(self.offsets.len() as u64); self.offsets.remove_all(&drop);
self.bump();
}
pub fn iter(&self) -> impl Iterator<Item = u32> + '_ {
self.offsets.offsets().into_iter()
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
struct FoldedRegion {
header: u32,
last: u32,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct InlineFold {
pub row: u32,
pub open: u32,
pub close: u32,
}
impl InlineFold {
#[must_use]
pub fn left_edge(&self) -> u32 {
crate::row_layout::gap_left_edge(self.open)
}
#[must_use]
pub fn right_edge(&self) -> u32 {
self.close
}
#[must_use]
pub fn hides_caret_at(&self, off: u32) -> bool {
crate::row_layout::gap_hides_caret(self.open, self.close, off)
}
#[must_use]
pub fn hides_glyph_at(&self, off: u32) -> bool {
crate::row_layout::gap_hides_glyph(self.open, self.close, off)
}
}
fn root_regions(mut regions: Vec<FoldedRegion>) -> Vec<FoldedRegion> {
regions.sort_by_key(|r| (r.header, core::cmp::Reverse(r.last)));
let mut roots: Vec<FoldedRegion> = Vec::new();
for r in regions {
if roots.last().is_none_or(|root| r.header > root.last) {
roots.push(r);
}
}
roots
}
fn build_inline(folds: Vec<InlineFold>) -> SumTree<InlineItem> {
let (mut prev_open, mut prev_row) = (0u32, 0u32);
let items = folds.into_iter().map(move |f| {
debug_assert!(f.open >= prev_open && f.row >= prev_row, "inline folds ascend");
let it = InlineItem { open_gap: f.open - prev_open, width: f.close - f.open, row_gap: f.row - prev_row };
prev_open = f.open;
prev_row = f.row;
it
});
SumTree::from_items(items)
}
fn root_inline(mut inline: Vec<InlineFold>) -> Vec<InlineFold> {
inline.sort_by(|a, b| a.open.cmp(&b.open).then(b.close.cmp(&a.close)));
let mut roots: Vec<InlineFold> = Vec::new();
for f in inline {
if roots.last().is_none_or(|r| !(r.row == f.row && r.open < f.open && f.close < r.close)) {
roots.push(f);
}
}
roots
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct VisibleRow {
pub display_row: DisplayRow,
pub buffer_row: BufferRow,
pub is_fold_header: bool,
pub last_folded: Option<BufferRow>,
}
#[derive(Clone, Copy, Debug)]
struct Region {
vgap: u32,
span: u32,
}
#[derive(Clone, Copy, Debug, Default)]
struct RegionSummary {
vgap_sum: u32,
span_sum: u32,
count: u32,
}
impl Summary for RegionSummary {
fn add_summary(&mut self, o: &Self) {
self.vgap_sum += o.vgap_sum;
self.span_sum += o.span_sum;
self.count += o.count;
}
}
impl Item for Region {
type Summary = RegionSummary;
fn summary(&self) -> RegionSummary {
RegionSummary { vgap_sum: self.vgap, span_sum: self.span, count: 1 }
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
struct LastDim(u32);
impl Dimension<RegionSummary> for LastDim {
fn add_summary(&mut self, s: &RegionSummary) {
self.0 += s.vgap_sum + s.span_sum;
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
struct DispDim(u32);
impl Dimension<RegionSummary> for DispDim {
fn add_summary(&mut self, s: &RegionSummary) {
self.0 += s.vgap_sum;
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
struct HiddenDim(u32);
impl Dimension<RegionSummary> for HiddenDim {
fn add_summary(&mut self, s: &RegionSummary) {
self.0 += s.span_sum;
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
struct RCount(u32);
impl Dimension<RegionSummary> for RCount {
fn add_summary(&mut self, s: &RegionSummary) {
self.0 += s.count;
}
}
struct Located {
header: u32,
last: u32,
span: u32,
hidden_before: u32,
}
#[derive(Clone, Copy, Debug)]
struct InlineItem {
open_gap: u32,
width: u32,
row_gap: u32,
}
#[derive(Clone, Copy, Debug, Default)]
struct InlineSummary {
open_span: u32,
row_span: u32,
count: u32,
}
impl Summary for InlineSummary {
fn add_summary(&mut self, o: &Self) {
self.open_span += o.open_span;
self.row_span += o.row_span;
self.count += o.count;
}
}
impl Item for InlineItem {
type Summary = InlineSummary;
fn summary(&self) -> InlineSummary {
InlineSummary { open_span: self.open_gap, row_span: self.row_gap, count: 1 }
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
struct OpenRow {
open: u32,
row: u32,
}
impl Dimension<InlineSummary> for OpenRow {
fn add_summary(&mut self, s: &InlineSummary) {
self.open += s.open_span;
self.row += s.row_span;
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
struct ICount(u32);
impl Dimension<InlineSummary> for ICount {
fn add_summary(&mut self, s: &InlineSummary) {
self.0 += s.count;
}
}
#[derive(Debug)]
pub struct FoldMap {
regions: SumTree<Region>,
inline: SumTree<InlineItem>,
buffer_row_count: u32,
}
impl PartialEq for FoldMap {
fn eq(&self, other: &Self) -> bool {
self.buffer_row_count == other.buffer_row_count
&& self.decoded_regions() == other.decoded_regions()
&& self.decoded_inline() == other.decoded_inline()
}
}
impl Eq for FoldMap {}
impl FoldMap {
#[must_use]
pub fn new(folds: &FoldSet, brackets: &Brackets, buffer: &Buffer) -> Self {
#[cfg(any(test, debug_assertions))]
FOLD_BUILDS.with(|c| c.set(c.get() + 1));
let mut regions = Vec::new();
let mut inline = Vec::new();
for open in folds.iter() {
let Some(close) = brackets.foldable_partner(open) else { continue };
let header = buffer.offset_to_point(open).row;
let last = buffer.offset_to_point(close).row;
if last > header {
regions.push(FoldedRegion { header, last }); } else if crate::row_layout::pair_has_interior(open, close) {
inline.push(InlineFold { row: header, open, close }); }
}
Self::assemble(regions, inline, buffer.line_count())
}
#[must_use]
pub fn apply_patch(&mut self, patch: &Patch, buffer: &Buffer) -> bool {
let new_rows = buffer.line_count();
let d = i64::from(new_rows) - i64::from(self.buffer_row_count);
if d != 0 {
let [edit] = patch.edits() else { return false };
let (os, oe) = (edit.old.start, edit.old.end);
if self.inline_overlaps(os, oe) {
return false;
}
let r_os = buffer.offset_to_point(edit.new.start).row;
let r_oe_old = (i64::from(buffer.offset_to_point(edit.new.end).row) - d) as u32;
let k = self.regions.summary_before(&LastDim(r_os.saturating_add(1))).count;
let (before, suffix) = self.regions.split_at(&RCount(k));
if !suffix.is_empty() {
let last_prev = before.extent::<LastDim>().0; let (first, ..) = suffix.seek::<RCount, LastDim>(&RCount(0)).expect("non-empty");
let header = last_prev + first.vgap;
if header < r_oe_old {
return false;
}
let new_vgap = i64::from(first.vgap) + d;
debug_assert!(new_vgap >= 0, "a non-straddling shift keeps vgap ≥ 0");
let fixed = Region { vgap: new_vgap as u32, span: first.span };
let suffix = suffix.replace(RCount(0)..RCount(1), std::iter::once(fixed));
self.regions = before.append(&suffix);
}
self.buffer_row_count = new_rows;
}
self.shift_inline(patch, d);
true
}
fn shift_inline(&mut self, patch: &Patch, d: i64) {
if let [edit] = patch.edits() {
let (os, oe) = (edit.old.start, edit.old.end);
if !self.inline_overlaps(os, oe) {
let byte_delta = (i64::from(edit.new.end) - i64::from(edit.new.start))
- (i64::from(oe) - i64::from(os));
let k = self.inline.summary_before(&OpenRow { open: os, row: 0 }).count; let (before, suffix) = self.inline.split_at(&ICount(k));
if !suffix.is_empty() {
let (it, ..) = suffix.seek::<ICount, OpenRow>(&ICount(0)).expect("non-empty");
let fixed = InlineItem {
open_gap: (i64::from(it.open_gap) + byte_delta) as u32,
width: it.width,
row_gap: (i64::from(it.row_gap) + d) as u32,
};
let suffix = suffix.replace(ICount(0)..ICount(1), std::iter::once(fixed));
self.inline = before.append(&suffix);
}
return;
}
}
debug_assert!(d == 0, "the inline fallback is only reached on a no-line-change edit");
let mut v = self.decoded_inline();
for f in &mut v {
f.open = patch.map_offset(f.open, Bias::Right);
f.close = patch.map_offset(f.close, Bias::Right);
}
self.inline = build_inline(v);
}
fn assemble(regions: Vec<FoldedRegion>, inline: Vec<InlineFold>, buffer_row_count: u32) -> Self {
let regions = root_regions(regions); let mut prev_last = 0u32;
let mut items = Vec::with_capacity(regions.len());
for r in ®ions {
items.push(Region { vgap: r.header - prev_last, span: r.last - r.header });
prev_last = r.last;
}
Self {
regions: SumTree::from_items(items),
inline: build_inline(root_inline(inline)),
buffer_row_count,
}
}
fn decoded_regions(&self) -> Vec<(u32, u32)> {
let mut out = Vec::with_capacity(self.regions.summary().count as usize);
let mut last = 0u32;
for item in self.regions.items() {
let header = last + item.vgap;
last = header + item.span;
out.push((header, last));
}
out
}
fn decoded_inline(&self) -> Vec<InlineFold> {
let mut out = Vec::with_capacity(self.inline.summary().count as usize);
let (mut open, mut row) = (0u32, 0u32);
for it in self.inline.items() {
crate::perf::charge(1); open += it.open_gap;
row += it.row_gap;
out.push(InlineFold { row, open, close: open + it.width });
}
out
}
#[must_use]
pub fn inline_fold_before(&self, off: u32) -> Option<InlineFold> {
let j = self.inline.summary_before(&OpenRow { open: off, row: 0 }).count; let (it, _c, OpenRow { open: ob, row: rb }) =
self.inline.seek::<ICount, OpenRow>(&ICount(j.checked_sub(1)?))?;
let open = ob + it.open_gap;
Some(InlineFold { row: rb + it.row_gap, open, close: open + it.width })
}
#[must_use]
pub fn inline_fold_at(&self, opener: u32) -> Option<InlineFold> {
self.inline_fold_before(opener.saturating_add(1)).filter(|f| f.open == opener)
}
fn inline_overlaps(&self, os: u32, oe: u32) -> bool {
let j = self.inline.summary_before(&OpenRow { open: oe.saturating_add(1), row: 0 }).count;
let Some(k) = j.checked_sub(1) else { return false };
let Some((it, _c, OpenRow { open: ob, .. })) = self.inline.seek::<ICount, OpenRow>(&ICount(k))
else {
return false;
};
ob + it.open_gap + it.width >= os }
fn locate(&self, r: u32) -> Option<Located> {
let (item, LastDim(last_prev), HiddenDim(hidden_before)) =
self.regions.seek::<LastDim, HiddenDim>(&LastDim(r))?;
let header = last_prev + item.vgap;
Some(Located { header, last: header + item.span, span: item.span, hidden_before })
}
fn containing(&self, r: u32) -> Option<Located> {
let (item, LastDim(last_prev), HiddenDim(hidden_before)) =
self.regions.seek::<LastDim, HiddenDim>(&LastDim(r.saturating_sub(1)))?;
let header = last_prev + item.vgap;
Some(Located { header, last: header + item.span, span: item.span, hidden_before })
}
#[must_use]
pub(crate) fn empty() -> Self {
Self::assemble(Vec::new(), Vec::new(), 0)
}
#[must_use]
pub fn inline_folds(&self) -> Vec<InlineFold> {
self.decoded_inline()
}
#[must_use]
pub fn inline_folds_on_row(&self, row: u32) -> Vec<InlineFold> {
let mut out = Vec::new();
self.inline.filter_visit::<OpenRow, _, _>(
&|before: &OpenRow, sum: &InlineSummary| {
before.row <= row && before.row + sum.row_span >= row
},
&mut |it: &InlineItem, before: &OpenRow| {
let r = before.row + it.row_gap;
if r == row {
let open = before.open + it.open_gap;
out.push(InlineFold { row: r, open, close: open + it.width });
}
},
);
out
}
fn hidden_total(&self) -> u32 {
self.regions.summary().span_sum
}
#[must_use]
pub fn display_row_count(&self) -> u32 {
self.buffer_row_count - self.hidden_total()
}
#[must_use]
pub fn max_display_row(&self) -> DisplayRow {
DisplayRow(self.display_row_count().saturating_sub(1))
}
#[must_use]
pub fn to_display_row(&self, row: BufferRow) -> DisplayRow {
let r = row.0;
let Some(l) = self.locate(r) else { return DisplayRow(r) };
if r > l.header && r <= l.last {
DisplayRow(l.header - l.hidden_before) } else if r <= l.header {
DisplayRow(r - l.hidden_before) } else {
DisplayRow(r - l.hidden_before - l.span) }
}
#[must_use]
pub fn to_buffer_row(&self, row: DisplayRow) -> BufferRow {
let d = row.0;
BufferRow(d + self.regions.summary_before(&DispDim(d)).span_sum)
}
#[must_use]
pub fn is_folded(&self, row: BufferRow) -> bool {
let r = row.0;
self.containing(r).is_some_and(|l| l.header < r && r <= l.last)
}
#[must_use]
pub fn entry_edge_if_hidden(&self, buffer: &Buffer, offset: u32) -> Option<u32> {
let row = buffer.offset_to_point(offset).row;
if let Some(l) = self.locate(row) {
if l.header < row && row < l.last {
let end = buffer.line_len(l.header);
return Some(buffer.point_to_offset(Point::new(l.header, end)));
}
}
if let Some(f) = self.inline_fold_before(offset) {
if f.hides_caret_at(offset) {
return Some(f.left_edge());
}
}
None
}
#[must_use]
pub fn fold_containing(&self, row: BufferRow) -> Option<(BufferRow, BufferRow)> {
let r = row.0;
self.containing(r)
.filter(|l| l.header < r && r <= l.last)
.map(|l| (BufferRow(l.header), BufferRow(l.last)))
}
#[must_use]
pub fn fold_at_header(&self, row: BufferRow) -> Option<BufferRow> {
self.locate(row.0).filter(|l| l.header == row.0).map(|l| BufferRow(l.last))
}
#[must_use]
pub fn header_of_tail(&self, row: BufferRow) -> Option<BufferRow> {
let r = row.0;
let j = self.regions.summary_before(&LastDim(r)).count;
let (item, RCount(_), LastDim(last_prev)) = self.regions.seek::<RCount, LastDim>(&RCount(j))?;
let last = last_prev + item.vgap + item.span;
(last == r).then(|| BufferRow(last - item.span))
}
#[must_use]
pub fn display_window(&self, top_rows: f64, bottom_rows: f64) -> core::ops::Range<u32> {
let first = top_rows.floor().max(0.0) as u32;
let last = (bottom_rows.ceil().max(0.0) as u32).min(self.display_row_count());
first..last.max(first)
}
pub fn visible_rows(&self, window: core::ops::Range<u32>) -> impl Iterator<Item = VisibleRow> + '_ {
window.map(move |d| {
let buffer_row = self.to_buffer_row(DisplayRow(d));
let last_folded = self.fold_at_header(buffer_row);
VisibleRow {
display_row: DisplayRow(d),
buffer_row,
is_fold_header: last_folded.is_some(),
last_folded,
}
})
}
#[cfg(test)]
pub(crate) fn from_rows(regions: impl IntoIterator<Item = (u32, u32)>, row_count: u32) -> Self {
let regions = regions
.into_iter()
.filter(|&(h, l)| l > h)
.map(|(header, last)| FoldedRegion { header, last })
.collect();
Self::assemble(regions, Vec::new(), row_count)
}
#[cfg(test)]
pub(crate) fn from_inline(
folds: impl IntoIterator<Item = (u32, u32, u32)>,
row_count: u32,
) -> Self {
let inline = folds.into_iter().map(|(row, open, close)| InlineFold { row, open, close }).collect();
Self::assemble(Vec::new(), inline, row_count)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::patch::{Edit, Patch};
#[test]
fn fold_toggle_and_duplicate() {
let mut f = FoldSet::new();
assert!(f.fold(10));
assert!(!f.fold(10), "same opener already folded");
assert!(f.is_folded(10));
assert!(f.toggle(10)); assert!(!f.is_folded(10));
assert!(f.is_empty());
}
#[test]
fn apply_patch_shifts_openers() {
let mut f = FoldSet::new();
f.fold(5);
f.fold(20);
f.apply_patch(&Patch::single(Edit { old: 0..0, new: 0..3 }));
assert_eq!(f.iter().collect::<Vec<_>>(), vec![8, 23]);
}
#[test]
fn reconcile_drops_openers_that_are_no_longer_foldable() {
let mut f = FoldSet::new();
f.fold(8);
f.fold(23);
f.reconcile(|o| o == 8);
assert_eq!(f.iter().collect::<Vec<_>>(), vec![8]);
}
#[test]
fn deleting_the_opener_reconciles_away() {
let mut f = FoldSet::new();
f.fold(4);
f.apply_patch(&Patch::single(Edit { old: 4..5, new: 4..4 }));
f.reconcile(|_| false); assert!(f.is_empty());
}
#[test]
fn empty_is_identity() {
let m = FoldMap::from_rows([], 6);
assert_eq!(m.display_row_count(), 6);
for r in 0..6 {
assert_eq!(m.to_display_row(BufferRow(r)), DisplayRow(r));
assert_eq!(m.to_buffer_row(DisplayRow(r)), BufferRow(r));
assert!(!m.is_folded(BufferRow(r)));
}
}
#[test]
fn hides_interior_rows() {
let m = FoldMap::from_rows([(2, 5)], 10); assert_eq!(m.display_row_count(), 7);
assert!(m.is_folded(BufferRow(3)) && m.is_folded(BufferRow(5)));
assert!(!m.is_folded(BufferRow(2)) && !m.is_folded(BufferRow(6)));
assert_eq!(m.fold_at_header(BufferRow(2)), Some(BufferRow(5)));
}
#[test]
fn display_and_buffer_row_mapping() {
let m = FoldMap::from_rows([(2, 5)], 10);
for r in [3, 4, 5] {
assert_eq!(m.to_display_row(BufferRow(r)), m.to_display_row(BufferRow(2)));
}
assert_eq!(m.to_display_row(BufferRow(6)), DisplayRow(3)); assert_eq!(m.to_buffer_row(DisplayRow(2)), BufferRow(2)); assert_eq!(m.to_buffer_row(DisplayRow(3)), BufferRow(6)); assert_eq!(m.to_buffer_row(DisplayRow(6)), BufferRow(9));
}
#[test]
fn roundtrip_visible_rows() {
let m = FoldMap::from_rows([(2, 5)], 10);
let all: Vec<_> = m.visible_rows(0..m.display_row_count()).collect();
assert_eq!(all.iter().map(|v| v.buffer_row.0).collect::<Vec<_>>(), vec![0, 1, 2, 6, 7, 8, 9]);
let header = all.iter().find(|v| v.is_fold_header).unwrap();
assert_eq!((header.buffer_row, header.last_folded), (BufferRow(2), Some(BufferRow(5))));
}
#[test]
fn nested_reduces_to_root() {
let m = FoldMap::from_rows([(1, 8), (3, 5)], 10); assert_eq!(m.display_row_count(), 3); assert_eq!(m.fold_at_header(BufferRow(1)), Some(BufferRow(8)));
assert_eq!(m.fold_at_header(BufferRow(3)), None, "inner header is hidden, not a root");
let visible: Vec<_> = m.visible_rows(0..m.display_row_count()).map(|v| v.buffer_row.0).collect();
assert_eq!(visible, vec![0, 1, 9]);
}
#[test]
fn three_level_nesting_reduces_to_root() {
let m = FoldMap::from_rows([(0, 10), (2, 8), (4, 6)], 12);
assert_eq!(m.display_row_count(), 2); assert_eq!(m.to_buffer_row(DisplayRow(1)), BufferRow(11));
}
#[test]
fn disjoint_siblings_both_hide() {
let m = FoldMap::from_rows([(2, 5), (6, 8)], 10);
assert_eq!(m.display_row_count(), 10 - 3 - 2);
assert_eq!(m.fold_at_header(BufferRow(2)), Some(BufferRow(5)));
assert_eq!(m.fold_at_header(BufferRow(6)), Some(BufferRow(8)));
}
#[test]
fn fold_at_eof() {
let m = FoldMap::from_rows([(3, 5)], 6);
assert_eq!(m.display_row_count(), 4); assert_eq!(m.fold_at_header(BufferRow(3)), Some(BufferRow(5)));
}
#[test]
fn header_of_tail_is_the_inverse_of_fold_at_header() {
let m = FoldMap::from_rows([(2, 5)], 10);
assert_eq!(m.fold_at_header(BufferRow(2)), Some(BufferRow(5)));
assert_eq!(m.header_of_tail(BufferRow(5)), Some(BufferRow(2)));
assert_eq!(m.header_of_tail(BufferRow(2)), None, "the header is not a tail");
assert_eq!(m.header_of_tail(BufferRow(4)), None, "an interior row is not the last");
}
#[test]
fn fold_containing_finds_the_enclosing_root() {
let m = FoldMap::from_rows([(2, 5)], 10);
assert_eq!(m.fold_containing(BufferRow(4)), Some((BufferRow(2), BufferRow(5))));
assert_eq!(m.fold_containing(BufferRow(2)), None, "the header is visible, not contained");
assert_eq!(m.fold_containing(BufferRow(6)), None);
}
#[test]
fn inline_shift_is_sublinear_in_fold_count() {
use crate::sum_tree::NODE_ALLOCS;
let allocs_for = |n: u32| -> f64 {
let mut m = FoldMap::from_inline((0..n).map(|i| (i, i * 10 + 100, i * 10 + 103)), n);
let buf = Buffer::new(&"\n".repeat((n - 1) as usize)).unwrap(); let patch = Patch::single(Edit { old: 5..5, new: 5..6 }); NODE_ALLOCS.with(|c| c.set(0));
let _ = m.apply_patch(&patch, &buf);
NODE_ALLOCS.with(std::cell::Cell::get) as f64
};
let (small, big) = (allocs_for(1000), allocs_for(4000));
eprintln!("[fold_map] inline shift node allocs {small} -> {big} ({:.2}x)", big / small);
assert!(
big <= small * 2.0,
"inline shift allocates superlinearly ({small} -> {big} nodes): it rebuilt the \
whole inline set instead of reanchoring the suffix at one seam"
);
}
#[test]
fn a_hidden_tail_row_counts_as_folded_with_a_fold_below() {
let m = FoldMap::from_rows([(2, 5), (6, 8)], 10);
assert!(m.is_folded(BufferRow(5)), "a fold's tail row is hidden");
assert_eq!(m.fold_containing(BufferRow(5)), Some((BufferRow(2), BufferRow(5))));
assert_eq!(m.to_display_row(BufferRow(5)), m.to_display_row(BufferRow(2)));
assert!(!m.is_folded(BufferRow(2)), "the header is visible");
assert!(m.is_folded(BufferRow(7)) && m.is_folded(BufferRow(8)), "second fold hidden");
}
#[test]
fn nested_inline_folds_reduce_to_the_outer_root() {
let roots = root_inline(vec![
InlineFold { row: 0, open: 20, close: 28 },
InlineFold { row: 0, open: 4, close: 30 },
]);
assert_eq!(roots.iter().map(|f| f.open).collect::<Vec<_>>(), vec![4], "only the outer chip renders");
}
#[test]
fn disjoint_inline_folds_both_survive() {
let roots = root_inline(vec![
InlineFold { row: 0, open: 12, close: 18 },
InlineFold { row: 0, open: 4, close: 8 },
]);
assert_eq!(roots.iter().map(|f| f.open).collect::<Vec<_>>(), vec![4, 12]);
}
#[test]
fn triple_nested_inline_reduces_to_root() {
let roots = root_inline(vec![
InlineFold { row: 0, open: 10, close: 20 },
InlineFold { row: 0, open: 5, close: 30 },
InlineFold { row: 0, open: 0, close: 40 },
]);
assert_eq!(roots.iter().map(|f| f.open).collect::<Vec<_>>(), vec![0]);
}
#[test]
fn inline_fold_at_matches_the_linear_membership() {
let mut seed = 0x9E37_79B9_7F4A_7C15u64;
let mut rng = || {
seed ^= seed << 13;
seed ^= seed >> 7;
seed ^= seed << 17;
seed
};
for _ in 0..300 {
let n = (rng() % 9) as usize;
let mut raw: Vec<(u32, u32, u32)> = Vec::new();
for _ in 0..n {
let row = (rng() % 4) as u32;
let open = row * 100 + (rng() % 80) as u32;
let width = 1 + (rng() % 18) as u32;
raw.push((row, open, open + width));
}
raw.sort_unstable();
raw.dedup_by_key(|&mut (_, o, _)| o); let m = FoldMap::from_inline(raw.iter().copied(), 4);
let roots = m.inline_folds();
for o in 0..=400u32 {
let linear = roots.iter().find(|f| f.open == o).copied();
let via_log = m.inline_fold_at(o);
assert_eq!(
via_log, linear,
"inline_fold_at({o}) = {via_log:?} but linear membership = {linear:?}\nroots = {roots:?}"
);
}
assert_eq!(m.inline_fold_at(u32::MAX), roots.iter().find(|f| f.open == u32::MAX).copied());
}
}
#[test]
fn inline_membership_is_fold_count_independent() {
let meter_for = |f: u32| -> u64 {
let m = FoldMap::from_inline((0..f).map(|i| (i, i * 10 + 100, i * 10 + 104)), f);
let mid = (f / 2) * 10 + 100;
crate::perf::reset();
let _ = m.inline_fold_at(mid);
crate::perf::meter()
};
let (small, big) = (meter_for(1000), meter_for(2000));
eprintln!("[fold_map] inline membership meter {small} -> {big}");
assert!(
big <= small + small / 4 + 256,
"inline_fold_at charged {small} -> {big}: membership decoded the whole inline set \
instead of an O(log F) offset-keyed descent"
);
}
}