pub type StyleId = u32;
pub const FOLD_PLACEHOLDER_STYLE_ID: StyleId = 0x0300_0001;
pub const DOCUMENT_HIGHLIGHT_TEXT_STYLE_ID: StyleId = 0x0400_0001;
pub const DOCUMENT_HIGHLIGHT_READ_STYLE_ID: StyleId = 0x0400_0002;
pub const DOCUMENT_HIGHLIGHT_WRITE_STYLE_ID: StyleId = 0x0400_0003;
pub const IME_MARKED_TEXT_STYLE_ID: StyleId = 0x0700_0001;
pub const INLAY_HINT_STYLE_ID: StyleId = 0x0800_0001;
pub const CODE_LENS_STYLE_ID: StyleId = 0x0800_0002;
pub const DOCUMENT_LINK_STYLE_ID: StyleId = 0x0800_0003;
pub const MATCH_HIGHLIGHT_STYLE_ID: StyleId = 0x0800_0004;
pub const DIFF_ADD_LINE_STYLE_ID: StyleId = 0x0900_0001;
pub const DIFF_REMOVE_LINE_STYLE_ID: StyleId = 0x0900_0002;
pub const DIFF_SPACER_STYLE_ID: StyleId = 0x0900_0003;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct StyleLayerId(pub u32);
impl StyleLayerId {
pub const fn new(id: u32) -> Self {
Self(id)
}
pub const SEMANTIC_TOKENS: Self = Self(1);
pub const SIMPLE_SYNTAX: Self = Self(2);
pub const SUBLIME_SYNTAX: Self = Self(3);
pub const DIAGNOSTICS: Self = Self(4);
pub const DOCUMENT_HIGHLIGHTS: Self = Self(5);
pub const TREE_SITTER: Self = Self(6);
pub const IME_MARKED_TEXT: Self = Self(7);
pub const DOCUMENT_LINKS: Self = Self(8);
pub const MATCH_HIGHLIGHTS: Self = Self(9);
pub const BRACKET_MATCHES: Self = Self(10);
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Interval {
pub start: usize,
pub end: usize,
pub style_id: StyleId,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IntervalTextEdit {
pub start: usize,
pub delete_len: usize,
pub insert_len: usize,
}
impl IntervalTextEdit {
pub const fn new(start: usize, delete_len: usize, insert_len: usize) -> Self {
Self {
start,
delete_len,
insert_len,
}
}
}
impl Interval {
pub fn new(start: usize, end: usize, style_id: StyleId) -> Self {
Self {
start,
end,
style_id,
}
}
pub fn contains(&self, pos: usize) -> bool {
self.start <= pos && pos < self.end
}
pub fn overlaps(&self, other: &Interval) -> bool {
self.start < other.end && other.start < self.end
}
}
pub struct IntervalTree {
intervals: Vec<Interval>,
prefix_max_end: Vec<usize>,
}
impl IntervalTree {
pub fn new() -> Self {
Self {
intervals: Vec::new(),
prefix_max_end: Vec::new(),
}
}
fn rebuild_prefix_max_end_from(&mut self, start_idx: usize) {
if self.intervals.is_empty() {
self.prefix_max_end.clear();
return;
}
if self.prefix_max_end.len() != self.intervals.len() {
self.prefix_max_end.resize(self.intervals.len(), 0);
}
let mut max_end = if start_idx == 0 {
0
} else {
self.prefix_max_end[start_idx - 1]
};
for (idx, interval) in self.intervals.iter().enumerate().skip(start_idx) {
max_end = max_end.max(interval.end);
self.prefix_max_end[idx] = max_end;
}
}
fn rebuild_prefix_max_end(&mut self) {
self.rebuild_prefix_max_end_from(0);
}
fn apply_insertion_to_interval(interval: &mut Interval, pos: usize, delta: usize) {
if interval.start >= pos {
interval.start += delta;
interval.end += delta;
} else if interval.end > pos {
interval.end += delta;
}
}
fn apply_deletion_to_interval(interval: &mut Interval, start: usize, end: usize) -> bool {
if start >= end {
return true;
}
let delta = end - start;
if interval.end <= start {
true
} else if interval.start >= end {
interval.start -= delta;
interval.end -= delta;
true
} else if interval.start >= start && interval.end <= end {
false
} else if interval.start < start && interval.end > end {
interval.end -= delta;
true
} else if interval.start < start {
interval.end = start;
true
} else {
interval.start = start;
interval.end -= delta;
true
}
}
#[cfg(debug_assertions)]
fn debug_assert_sorted(&self) {
debug_assert!(
self.intervals
.windows(2)
.all(|pair| pair[0].start <= pair[1].start),
"IntervalTree intervals must remain sorted by start offset"
);
}
pub fn insert(&mut self, interval: Interval) {
let pos = self
.intervals
.binary_search_by_key(&interval.start, |i| i.start)
.unwrap_or_else(|pos| pos);
self.intervals.insert(pos, interval);
self.prefix_max_end.insert(pos, 0);
self.rebuild_prefix_max_end_from(pos);
}
pub fn remove(&mut self, start: usize, end: usize, style_id: StyleId) -> bool {
if let Some(pos) = self
.intervals
.iter()
.position(|i| i.start == start && i.end == end && i.style_id == style_id)
{
self.intervals.remove(pos);
self.prefix_max_end.remove(pos);
if pos < self.intervals.len() {
self.rebuild_prefix_max_end_from(pos);
}
true
} else {
false
}
}
pub fn query_point(&self, pos: usize) -> Vec<&Interval> {
self.query_point_impl(pos).0
}
fn query_point_impl(&self, pos: usize) -> (Vec<&Interval>, usize) {
if self.intervals.is_empty() {
return (Vec::new(), 0);
}
let mut result = Vec::new();
let mut scanned = 0usize;
let search_key = pos.saturating_add(1);
let idx = match self
.intervals
.binary_search_by_key(&search_key, |i| i.start)
{
Ok(idx) => idx,
Err(idx) => idx,
};
for i in (0..idx).rev() {
scanned = scanned.saturating_add(1);
if self.prefix_max_end[i] <= pos {
break;
}
let interval = &self.intervals[i];
if interval.contains(pos) {
result.push(interval);
}
}
(result, scanned)
}
#[cfg(test)]
fn query_point_scan_count(&self, pos: usize) -> usize {
self.query_point_impl(pos).1
}
pub fn query_range(&self, start: usize, end: usize) -> Vec<&Interval> {
if self.intervals.is_empty() || start >= end {
return Vec::new();
}
let mut result = Vec::new();
let search_end = match self.intervals.binary_search_by_key(&end, |i| i.start) {
Ok(idx) => idx,
Err(idx) => idx,
};
if search_end == 0 {
return result;
}
let mut scan_start = match self.intervals.binary_search_by_key(&start, |i| i.start) {
Ok(idx) | Err(idx) => idx.min(search_end),
};
while scan_start > 0 && self.prefix_max_end[scan_start - 1] > start {
scan_start -= 1;
}
for interval in self.intervals[scan_start..search_end].iter() {
if interval.start < end && interval.end > start {
result.push(interval);
}
}
result
}
pub fn clear(&mut self) {
self.intervals.clear();
self.prefix_max_end.clear();
}
pub fn len(&self) -> usize {
self.intervals.len()
}
pub fn is_empty(&self) -> bool {
self.intervals.is_empty()
}
pub fn update_for_insertion(&mut self, pos: usize, delta: usize) {
if delta == 0 || self.intervals.is_empty() {
return;
}
for interval in &mut self.intervals {
Self::apply_insertion_to_interval(interval, pos, delta);
}
self.rebuild_prefix_max_end();
#[cfg(debug_assertions)]
self.debug_assert_sorted();
}
pub fn update_for_deletion(&mut self, start: usize, end: usize) {
if start >= end || self.intervals.is_empty() {
return;
}
let mut updated = Vec::with_capacity(self.intervals.len());
for mut interval in self.intervals.drain(..) {
if Self::apply_deletion_to_interval(&mut interval, start, end) {
updated.push(interval);
}
}
if !updated
.windows(2)
.all(|pair| pair[0].start <= pair[1].start)
{
updated.sort_by_key(|interval| interval.start);
}
self.intervals = updated;
self.rebuild_prefix_max_end();
#[cfg(debug_assertions)]
self.debug_assert_sorted();
}
pub fn update_for_text_edits(&mut self, edits: &[IntervalTextEdit]) {
if self.intervals.is_empty()
|| !edits
.iter()
.any(|edit| edit.delete_len > 0 || edit.insert_len > 0)
{
return;
}
let mut ordered_edits: Vec<IntervalTextEdit> = edits
.iter()
.copied()
.filter(|edit| edit.delete_len > 0 || edit.insert_len > 0)
.collect();
ordered_edits.sort_by_key(|edit| std::cmp::Reverse(edit.start));
let mut updated = Vec::with_capacity(self.intervals.len());
for mut interval in self.intervals.drain(..) {
let mut keep = true;
for edit in &ordered_edits {
if edit.delete_len > 0 {
let end = edit.start.saturating_add(edit.delete_len);
keep = Self::apply_deletion_to_interval(&mut interval, edit.start, end);
if !keep {
break;
}
}
if edit.insert_len > 0 {
Self::apply_insertion_to_interval(&mut interval, edit.start, edit.insert_len);
}
}
if keep {
updated.push(interval);
}
}
if !updated
.windows(2)
.all(|pair| pair[0].start <= pair[1].start)
{
updated.sort_by_key(|interval| interval.start);
}
self.intervals = updated;
self.rebuild_prefix_max_end();
#[cfg(debug_assertions)]
self.debug_assert_sorted();
}
}
impl Default for IntervalTree {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FoldRegion {
pub start_line: usize,
pub end_line: usize,
pub is_collapsed: bool,
pub placeholder: String,
}
impl FoldRegion {
pub fn new(start_line: usize, end_line: usize) -> Self {
Self {
start_line,
end_line,
is_collapsed: false,
placeholder: String::from("[...]"),
}
}
pub fn with_placeholder(start_line: usize, end_line: usize, placeholder: String) -> Self {
Self {
start_line,
end_line,
is_collapsed: false,
placeholder,
}
}
pub fn expand(&mut self) {
self.is_collapsed = false;
}
pub fn collapse(&mut self) {
self.is_collapsed = true;
}
pub fn toggle(&mut self) {
self.is_collapsed = !self.is_collapsed;
}
pub fn contains_line(&self, line: usize) -> bool {
line >= self.start_line && line <= self.end_line
}
}
pub struct FoldingManager {
derived_regions: Vec<FoldRegion>,
user_regions: Vec<FoldRegion>,
merged_regions: Vec<FoldRegion>,
}
impl FoldingManager {
pub fn new() -> Self {
Self {
derived_regions: Vec::new(),
user_regions: Vec::new(),
merged_regions: Vec::new(),
}
}
fn rebuild_merged_regions(&mut self) {
self.merged_regions.clear();
self.merged_regions
.extend(self.user_regions.iter().cloned());
self.merged_regions
.extend(self.derived_regions.iter().cloned());
self.merged_regions
.sort_by_key(|r| (r.start_line, r.end_line));
self.merged_regions
.dedup_by(|a, b| a.start_line == b.start_line && a.end_line == b.end_line);
}
fn normalize_regions(regions: &mut Vec<FoldRegion>) {
regions.sort_by_key(|r| (r.start_line, r.end_line));
regions.dedup_by(|a, b| a.start_line == b.start_line && a.end_line == b.end_line);
regions.retain(|r| r.end_line > r.start_line);
}
fn clamp_regions(regions: &mut Vec<FoldRegion>, max_line: usize) {
for r in regions.iter_mut() {
r.start_line = r.start_line.min(max_line);
r.end_line = r.end_line.min(max_line);
}
Self::normalize_regions(regions);
}
fn overlapping_line_count(left: &FoldRegion, right: &FoldRegion) -> usize {
let start = left.start_line.max(right.start_line);
let end = left.end_line.min(right.end_line);
if start > end { 0 } else { end - start + 1 }
}
fn collapsed_fuzzy_match_score(
existing: &FoldRegion,
replacement: &FoldRegion,
) -> Option<(usize, usize, usize)> {
if existing.placeholder != replacement.placeholder {
return None;
}
let start_delta = existing.start_line.abs_diff(replacement.start_line);
let overlapping_line_count = Self::overlapping_line_count(existing, replacement);
if start_delta > 1 || overlapping_line_count < 2 {
return None;
}
let existing_len = existing.end_line.saturating_sub(existing.start_line);
let replacement_len = replacement.end_line.saturating_sub(replacement.start_line);
let len_delta = existing_len.abs_diff(replacement_len);
let max_len_delta = 2.max(existing_len.max(replacement_len) / 3);
if len_delta > max_len_delta {
return None;
}
Some((
start_delta,
len_delta,
existing.end_line.abs_diff(replacement.end_line),
))
}
fn preserve_collapsed_states(existing: &[FoldRegion], replacements: &mut [FoldRegion]) {
let collapsed = existing
.iter()
.filter(|region| region.is_collapsed)
.collect::<Vec<_>>();
if collapsed.is_empty() {
return;
}
let mut used = vec![false; collapsed.len()];
for replacement in replacements.iter_mut() {
if let Some(source_idx) = collapsed.iter().enumerate().position(|(idx, existing)| {
!used[idx]
&& existing.start_line == replacement.start_line
&& existing.end_line == replacement.end_line
}) {
replacement.is_collapsed = true;
used[source_idx] = true;
}
}
for replacement in replacements
.iter_mut()
.filter(|region| !region.is_collapsed)
{
let best = collapsed
.iter()
.enumerate()
.filter_map(|(idx, existing)| {
if used[idx] {
return None;
}
Self::collapsed_fuzzy_match_score(existing, replacement)
.map(|score| (score, idx))
})
.min_by_key(|(score, idx)| (*score, *idx));
if let Some((_score, source_idx)) = best {
replacement.is_collapsed = true;
used[source_idx] = true;
}
}
}
pub fn add_region(&mut self, region: FoldRegion) {
let pos = self
.user_regions
.binary_search_by_key(®ion.start_line, |r| r.start_line)
.unwrap_or_else(|pos| pos);
self.user_regions.insert(pos, region);
Self::normalize_regions(&mut self.user_regions);
self.rebuild_merged_regions();
}
pub fn remove_region(&mut self, start_line: usize, end_line: usize) -> bool {
if let Some(pos) = self
.user_regions
.iter()
.position(|r| r.start_line == start_line && r.end_line == end_line)
{
self.user_regions.remove(pos);
self.rebuild_merged_regions();
true
} else {
false
}
}
pub fn get_region_for_line(&self, line: usize) -> Option<&FoldRegion> {
self.merged_regions.iter().find(|r| r.contains_line(line))
}
pub(crate) fn innermost_region_bounds_for_line(&self, line: usize) -> Option<(usize, usize)> {
self.merged_regions
.iter()
.filter(|region| region.contains_line(line))
.min_by_key(|region| {
(
region.end_line.saturating_sub(region.start_line),
region.end_line,
region.start_line,
)
})
.map(|region| (region.start_line, region.end_line))
}
pub fn get_region_for_line_mut(&mut self, line: usize) -> Option<&mut FoldRegion> {
let mut best: Option<(
bool, /*is_user*/
usize, /*idx*/
usize, /*len*/
usize, /*end*/
usize, /*start*/
)> = None;
for (is_user, regions) in [
(true, self.user_regions.as_slice()),
(false, self.derived_regions.as_slice()),
] {
for (idx, region) in regions.iter().enumerate() {
if !region.contains_line(line) {
continue;
}
let len = region.end_line.saturating_sub(region.start_line);
let candidate = (is_user, idx, len, region.end_line, region.start_line);
match best {
None => best = Some(candidate),
Some((best_is_user, _best_idx, best_len, best_end, best_start)) => {
let better_range = (len, region.end_line, region.start_line)
< (best_len, best_end, best_start);
let prefer_user_tie = (len, region.end_line, region.start_line)
== (best_len, best_end, best_start)
&& is_user
&& !best_is_user;
if better_range || prefer_user_tie {
best = Some(candidate);
}
}
}
}
}
let (is_user, idx, _len, _end, _start) = best?;
if is_user {
self.user_regions.get_mut(idx)
} else {
self.derived_regions.get_mut(idx)
}
}
pub fn collapse_line(&mut self, line: usize) -> bool {
if let Some(region) = self.get_region_for_line_mut(line) {
region.collapse();
self.rebuild_merged_regions();
true
} else {
false
}
}
pub fn expand_line(&mut self, line: usize) -> bool {
if let Some(region) = self.get_region_for_line_mut(line) {
region.expand();
self.rebuild_merged_regions();
true
} else {
false
}
}
pub fn toggle_line(&mut self, line: usize) -> bool {
if let Some(region) = self.get_region_for_line_mut(line) {
region.toggle();
self.rebuild_merged_regions();
true
} else {
false
}
}
pub fn toggle_region_starting_at_line(&mut self, start_line: usize) -> bool {
if self.merged_regions.is_empty() {
return false;
}
let mut best_source = None::<(bool, usize)>; let mut best_end = usize::MAX;
for (is_user, regions) in [
(true, &mut self.user_regions),
(false, &mut self.derived_regions),
] {
let Ok(mut idx) = regions.binary_search_by_key(&start_line, |r| r.start_line) else {
continue;
};
while idx > 0 && regions[idx - 1].start_line == start_line {
idx -= 1;
}
for (i, region) in regions[idx..].iter().enumerate() {
if region.start_line != start_line {
break;
}
if region.end_line <= region.start_line {
continue;
}
if region.end_line < best_end
|| (region.end_line == best_end
&& best_source.is_some_and(|(prev_is_user, _)| !prev_is_user && is_user))
{
best_end = region.end_line;
best_source = Some((is_user, i));
}
}
}
let Some((is_user, idx)) = best_source else {
return false;
};
if is_user {
if let Some(region) = self.user_regions.get_mut(idx) {
region.toggle();
}
} else if let Some(region) = self.derived_regions.get_mut(idx) {
region.toggle();
}
self.rebuild_merged_regions();
true
}
pub fn logical_to_visual(&self, logical_line: usize, base_visual: usize) -> Option<usize> {
let mut hidden_lines = 0usize;
for (hidden_start, hidden_end) in self.collapsed_hidden_ranges() {
if logical_line >= hidden_start && logical_line < hidden_end {
return None;
}
if logical_line <= hidden_start {
break;
}
hidden_lines = hidden_lines.saturating_add(hidden_end.min(logical_line) - hidden_start);
}
Some(base_visual.saturating_add(logical_line.saturating_sub(hidden_lines)))
}
pub fn visual_to_logical(&self, visual_line: usize, base_visual: usize) -> usize {
let mut logical = visual_line.saturating_sub(base_visual);
for (hidden_start, hidden_end) in self.collapsed_hidden_ranges() {
if logical < hidden_start {
break;
}
logical = logical.saturating_add(hidden_end - hidden_start);
}
logical
}
fn collapsed_hidden_ranges(&self) -> Vec<(usize, usize)> {
let mut ranges: Vec<(usize, usize)> = Vec::new();
for region in self
.merged_regions
.iter()
.filter(|region| region.is_collapsed)
{
if region.end_line <= region.start_line {
continue;
}
let hidden_start = region.start_line.saturating_add(1);
let hidden_end = region.end_line.saturating_add(1);
if hidden_start >= hidden_end {
continue;
}
if let Some((_, last_end)) = ranges.last_mut()
&& hidden_start <= *last_end
{
*last_end = (*last_end).max(hidden_end);
continue;
}
ranges.push((hidden_start, hidden_end));
}
ranges
}
pub fn regions(&self) -> &[FoldRegion] {
&self.merged_regions
}
pub fn derived_regions(&self) -> &[FoldRegion] {
&self.derived_regions
}
pub fn user_regions(&self) -> &[FoldRegion] {
&self.user_regions
}
pub fn clear(&mut self) {
self.derived_regions.clear();
self.user_regions.clear();
self.merged_regions.clear();
}
pub fn clear_derived_regions(&mut self) {
self.derived_regions.clear();
self.rebuild_merged_regions();
}
pub fn replace_derived_regions(&mut self, mut regions: Vec<FoldRegion>) {
Self::normalize_regions(&mut regions);
self.derived_regions = regions;
self.rebuild_merged_regions();
}
pub fn replace_derived_regions_preserving_collapsed(&mut self, mut regions: Vec<FoldRegion>) {
Self::normalize_regions(&mut regions);
Self::preserve_collapsed_states(&self.derived_regions, &mut regions);
self.derived_regions = regions;
self.rebuild_merged_regions();
}
pub fn replace_regions(&mut self, regions: Vec<FoldRegion>) {
self.replace_derived_regions(regions);
}
pub fn expand_all(&mut self) {
for region in &mut self.derived_regions {
region.expand();
}
for region in &mut self.user_regions {
region.expand();
}
self.rebuild_merged_regions();
}
pub fn collapse_all(&mut self) {
for region in &mut self.derived_regions {
region.collapse();
}
for region in &mut self.user_regions {
region.collapse();
}
self.rebuild_merged_regions();
}
pub fn apply_line_delta(&mut self, edit_line: usize, line_delta: isize) {
if line_delta == 0 {
return;
}
let apply = |regions: &mut Vec<FoldRegion>| {
for region in regions.iter_mut() {
if edit_line <= region.start_line {
let start = region.start_line as isize + line_delta;
let end = region.end_line as isize + line_delta;
region.start_line = start.max(0) as usize;
region.end_line = end.max(0) as usize;
} else if edit_line <= region.end_line {
let end = region.end_line as isize + line_delta;
region.end_line = end.max(region.start_line as isize) as usize;
}
}
};
apply(&mut self.derived_regions);
apply(&mut self.user_regions);
self.rebuild_merged_regions();
}
pub fn clamp_to_line_count(&mut self, line_count: usize) {
let max_line = line_count.saturating_sub(1);
Self::clamp_regions(&mut self.derived_regions, max_line);
Self::clamp_regions(&mut self.user_regions, max_line);
self.rebuild_merged_regions();
}
}
impl Default for FoldingManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_interval_contains() {
let interval = Interval::new(10, 20, 1);
assert!(interval.contains(10));
assert!(interval.contains(15));
assert!(interval.contains(19));
assert!(!interval.contains(20));
assert!(!interval.contains(9));
}
#[test]
fn test_interval_overlaps() {
let i1 = Interval::new(10, 20, 1);
let i2 = Interval::new(15, 25, 2);
let i3 = Interval::new(25, 30, 3);
assert!(i1.overlaps(&i2));
assert!(i2.overlaps(&i1));
assert!(!i1.overlaps(&i3));
assert!(!i3.overlaps(&i1));
}
#[test]
fn test_interval_tree_insert() {
let mut tree = IntervalTree::new();
tree.insert(Interval::new(10, 20, 1));
tree.insert(Interval::new(5, 15, 2));
tree.insert(Interval::new(15, 25, 3));
assert_eq!(tree.len(), 3);
}
#[test]
fn test_interval_tree_query_point() {
let mut tree = IntervalTree::new();
tree.insert(Interval::new(10, 20, 1));
tree.insert(Interval::new(5, 15, 2));
tree.insert(Interval::new(15, 25, 3));
let results = tree.query_point(12);
assert_eq!(results.len(), 2);
let results = tree.query_point(18);
assert_eq!(results.len(), 2); }
#[test]
fn test_interval_tree_query_point_prunes_scan() {
let mut tree = IntervalTree::new();
for i in 0..10_000usize {
let start = i * 2;
tree.insert(Interval::new(start, start + 1, 1));
}
let pos = 2 * 10_000 - 2; let results = tree.query_point(pos);
assert_eq!(results.len(), 1);
assert!(
tree.query_point_scan_count(pos) <= 4,
"scan should be pruned for disjoint intervals"
);
}
#[test]
fn test_interval_tree_query_range() {
let mut tree = IntervalTree::new();
tree.insert(Interval::new(10, 20, 1));
tree.insert(Interval::new(25, 35, 2));
tree.insert(Interval::new(40, 50, 3));
let results = tree.query_range(15, 30);
assert_eq!(results.len(), 2);
let results = tree.query_range(0, 60);
assert_eq!(results.len(), 3); }
#[test]
fn test_interval_tree_update_insertion() {
let mut tree = IntervalTree::new();
tree.insert(Interval::new(10, 20, 1));
tree.insert(Interval::new(30, 40, 2));
tree.update_for_insertion(15, 5);
assert_eq!(tree.intervals[0].start, 10);
assert_eq!(tree.intervals[0].end, 25);
assert_eq!(tree.intervals[1].start, 35); assert_eq!(tree.intervals[1].end, 45); }
#[test]
fn test_interval_tree_update_deletion() {
let mut tree = IntervalTree::new();
tree.insert(Interval::new(10, 20, 1));
tree.insert(Interval::new(30, 40, 2));
tree.insert(Interval::new(50, 60, 3));
tree.update_for_deletion(25, 35);
assert_eq!(tree.intervals[0].start, 10);
assert_eq!(tree.intervals[0].end, 20);
assert_eq!(tree.intervals[1].start, 25); assert_eq!(tree.intervals[1].end, 30);
assert_eq!(tree.intervals[2].start, 40); assert_eq!(tree.intervals[2].end, 50); }
#[test]
fn test_interval_tree_batch_update_matches_sequential_updates() {
fn build_tree() -> IntervalTree {
let mut tree = IntervalTree::new();
tree.insert(Interval::new(0, 4, 1));
tree.insert(Interval::new(6, 12, 2));
tree.insert(Interval::new(15, 25, 3));
tree.insert(Interval::new(20, 24, 4));
tree.insert(Interval::new(30, 38, 5));
tree.insert(Interval::new(38, 45, 6));
tree
}
let edits = vec![
IntervalTextEdit::new(40, 5, 2),
IntervalTextEdit::new(18, 10, 0),
IntervalTextEdit::new(5, 0, 3),
];
let mut sequential = build_tree();
for edit in &edits {
if edit.delete_len > 0 {
sequential.update_for_deletion(edit.start, edit.start + edit.delete_len);
}
if edit.insert_len > 0 {
sequential.update_for_insertion(edit.start, edit.insert_len);
}
}
let mut batched = build_tree();
batched.update_for_text_edits(&edits);
assert_eq!(batched.intervals, sequential.intervals);
assert_eq!(batched.prefix_max_end, sequential.prefix_max_end);
}
#[test]
fn test_interval_tree_batch_update_keeps_queries_correct() {
let mut tree = IntervalTree::new();
tree.insert(Interval::new(2, 8, 1));
tree.insert(Interval::new(10, 18, 2));
tree.insert(Interval::new(20, 30, 3));
tree.update_for_text_edits(&[
IntervalTextEdit::new(12, 4, 1),
IntervalTextEdit::new(4, 2, 0),
]);
let point_styles: Vec<StyleId> = tree
.query_point(10)
.into_iter()
.map(|interval| interval.style_id)
.collect();
assert_eq!(point_styles, vec![2]);
let range_styles: Vec<StyleId> = tree
.query_range(0, 32)
.into_iter()
.map(|interval| interval.style_id)
.collect();
assert_eq!(range_styles, vec![1, 2, 3]);
}
#[test]
fn test_fold_region() {
let mut region = FoldRegion::new(5, 10);
assert!(!region.is_collapsed);
region.collapse();
assert!(region.is_collapsed);
region.expand();
assert!(!region.is_collapsed);
region.toggle();
assert!(region.is_collapsed);
}
#[test]
fn test_folding_manager() {
let mut manager = FoldingManager::new();
manager.add_region(FoldRegion::new(5, 10));
manager.add_region(FoldRegion::new(15, 20));
assert!(manager.collapse_line(7));
assert!(manager.get_region_for_line(7).unwrap().is_collapsed);
assert!(manager.expand_line(7));
assert!(!manager.get_region_for_line(7).unwrap().is_collapsed);
}
#[test]
fn test_nested_folds_can_unfold_inner_after_outer_unfold() {
let mut manager = FoldingManager::new();
let mut outer = FoldRegion::new(1, 10);
outer.collapse();
manager.add_region(outer);
let mut inner = FoldRegion::new(3, 5);
inner.collapse();
manager.add_region(inner);
let inner_before = manager
.regions()
.iter()
.find(|r| r.start_line == 3 && r.end_line == 5)
.unwrap();
assert!(inner_before.is_collapsed);
assert!(manager.expand_line(1));
let outer_after = manager
.regions()
.iter()
.find(|r| r.start_line == 1 && r.end_line == 10)
.unwrap();
assert!(!outer_after.is_collapsed);
assert!(manager.expand_line(3));
let inner_after = manager
.regions()
.iter()
.find(|r| r.start_line == 3 && r.end_line == 5)
.unwrap();
assert!(!inner_after.is_collapsed);
}
#[test]
fn test_logical_to_visual_with_folding() {
let mut manager = FoldingManager::new();
let mut region = FoldRegion::new(5, 10);
region.collapse();
manager.add_region(region);
assert_eq!(manager.logical_to_visual(3, 0), Some(3));
assert_eq!(manager.logical_to_visual(5, 0), Some(5));
assert_eq!(manager.logical_to_visual(7, 0), None);
assert_eq!(manager.logical_to_visual(15, 0), Some(10)); }
#[test]
fn test_multiple_overlapping_styles() {
let mut tree = IntervalTree::new();
tree.insert(Interval::new(0, 100, 1)); tree.insert(Interval::new(20, 30, 2)); tree.insert(Interval::new(25, 35, 3));
let styles = tree.query_point(27);
assert_eq!(styles.len(), 3);
let style_ids: Vec<StyleId> = styles.iter().map(|i| i.style_id).collect();
assert!(style_ids.contains(&1));
assert!(style_ids.contains(&2));
assert!(style_ids.contains(&3));
}
}