diff --git a/crates/editor/src/display_map/filter_map.rs b/crates/editor/src/display_map/filter_map.rs index 0a1ae8fa4b9cdd01d0726400c130122bb6b241b0..8b1189455c8022e72cf89e6fd0165b93f5f4dd34 100644 --- a/crates/editor/src/display_map/filter_map.rs +++ b/crates/editor/src/display_map/filter_map.rs @@ -3,6 +3,7 @@ use std::{cmp, ops::Range}; use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind}; use multi_buffer::{AnchorRangeExt as _, MultiBufferSnapshot}; use rope::{Point, TextSummary}; +use schemars::transform; use sum_tree::{Dimensions, SumTree}; use text::Bias; use util::debug_panic; @@ -102,6 +103,15 @@ struct FilterSnapshot { #[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)] struct FilterOffset(usize); +impl FilterOffset { + /// Convert a range of offsets to a range of [`FilterOffset`]s, given that + /// there are no filtered lines in the buffer (for example, if no + /// [`FilterMode`] is set). + pub fn naive_range(Range { start, end }: Range) -> Range { + FilterOffset(start)..FilterOffset(end) + } +} + impl sum_tree::Dimension<'_, TransformSummary> for FilterOffset { fn zero(cx: ::Context<'_>) -> Self { FilterOffset(0) @@ -172,7 +182,10 @@ impl FilterMap { fn check_invariants(&self) { use itertools::Itertools; - assert_eq!( + #[cfg(any(test, feature = "test-support"))] + log::info!("filter map output text:\n{}", self.snapshot.text()); + + pretty_assertions::assert_eq!( self.snapshot.transforms.summary().input.0, self.snapshot.buffer_snapshot.text_summary(), "input summary does not match buffer snapshot" @@ -194,12 +207,12 @@ impl FilterMap { }); let Some(mode) = self.mode else { - assert_eq!( + pretty_assertions::assert_eq!( self.snapshot.transforms.iter().count(), 1, "more than one transform in a trivial map" ); - assert_eq!( + pretty_assertions::assert_eq!( self.snapshot.transforms.summary().output.0, self.snapshot.buffer_snapshot.text_summary(), "output summary for trivial map does not match buffer snapshot" @@ -227,7 +240,7 @@ impl FilterMap { .buffer_snapshot .text_summary_for_range::(anchor..multi_buffer::Anchor::max()); - assert_eq!( + pretty_assertions::assert_eq!( self.snapshot.transforms.summary().output.0, expected_summary, "wrong output summary for nontrivial map" @@ -242,6 +255,8 @@ impl FilterMap { buffer_edits: Vec>, ) -> (FilterSnapshot, Vec) { let Some(mode) = self.mode else { + // If we're not filtering out anything, edits can be passed through + // unchanged and we only need one isomorphic transform. self.snapshot.buffer_snapshot = buffer_snapshot.clone(); self.snapshot.transforms = SumTree::from_item( Transform::Isomorphic { @@ -254,15 +269,17 @@ impl FilterMap { buffer_edits .into_iter() .map(|edit| text::Edit { - old: FilterOffset(edit.old.start)..FilterOffset(edit.old.end), - new: FilterOffset(edit.new.start)..FilterOffset(edit.new.end), + old: FilterOffset::naive_range(edit.old), + new: FilterOffset::naive_range(edit.new), }) .collect(), ); }; - let mut new_transforms = SumTree::new(()); - let mut cursor = self + dbg!(&buffer_edits); + + let mut new_transforms: SumTree = SumTree::new(()); + let mut transform_cursor = self .snapshot .transforms .cursor::>(()); @@ -271,32 +288,104 @@ impl FilterMap { // TODO in what follows we repeatedly call text_summary_for_range, // could use a persistent usize cursor over buffer_snapshot instead. - for buffer_edit in buffer_edits { + transform_cursor.next(); + + // for e1 in &buffer_edits { + // for e2 in &buffer_edits { + // assert!(!e1.old.overlaps(e2.old)); + // } + // } + // + // Edit { old: 3..6, new: 10..20 } -> Edit { old: 10..20, new: something_else } + // break at old = 4 + // old = 3..4, 4..6 + // new = 10..20, 10..20 + // new = 10..20, 20..20 + // new = ?????? + + // convert vec to vecdeque + // iterate through edits + // check if an edit crosses a transform boundary + // if it does, truncate, and push the other half to the front of the queue + + // |--------------------------------------| len = 216 self.snapshot.transforms (cursor) SumTree + // <-----> <--------------> <------> <-------> <----> + // <-----> <--------------> <------> <><-----> <----> + // <--> + // + // | + // | + // | + // + // |---------| |-----------| |------------| new_transforms: SumTree + // + // first iteration should give us new transforms like: + // + // + + let mut buffer_edits = buffer_edits.into_iter().peekable(); + + while let Some(buffer_edit) = buffer_edits.next() { + debug_assert!(transform_cursor.start().0 <= buffer_edit.old.end); + + dbg!( + transform_cursor.start(), + buffer_edit.old.end, + transform_cursor.end() + ); // Reuse any old transforms that strictly precede the start of the edit. - new_transforms.append(cursor.slice(&buffer_edit.old.end, Bias::Right), ()); + log::info!( + "input len before append is {}", + new_transforms.summary().input.0.len + ); + new_transforms.append(transform_cursor.slice(&buffer_edit.old.end, Bias::Left), ()); + log::info!( + "input len after append is {}", + new_transforms.summary().input.0.len + ); + dbg!( + transform_cursor.start(), + buffer_edit.old.end, + transform_cursor.end() + ); - let mut edit_old_start = cursor.start().1; + let mut edit_old_start = transform_cursor.start().1; let mut edit_new_start = FilterOffset(new_transforms.summary().output.0.len); // If the edit starts in the middle of a transform, split the transform and push the unaffected portion. - if buffer_edit.old.start > cursor.start().0 { - let summary = self - .snapshot - .buffer_snapshot - .text_summary_for_range(cursor.start().0..buffer_edit.old.start); - match cursor.item() { + if buffer_edit.new.start > new_transforms.summary().input.0.len { + let summary = buffer_snapshot.text_summary_for_range( + new_transforms.summary().input.0.len..buffer_edit.new.start, + ); + match transform_cursor.item() { Some(Transform::Isomorphic { .. }) => { push_isomorphic(&mut new_transforms, summary); edit_old_start.0 += summary.len; edit_new_start.0 += summary.len; - cursor.next(); } Some(Transform::Filter { .. }) => { push_filter(&mut new_transforms, summary); - cursor.next(); } None => {} } + // let summary = self + // .snapshot + // .buffer_snapshot + // .text_summary_for_range(transform_cursor.start().0..buffer_edit.old.start); + // match transform_cursor.item() { + // Some(Transform::Isomorphic { .. }) => { + // push_isomorphic(&mut new_transforms, summary); + // edit_old_start.0 += summary.len; + // edit_new_start.0 += summary.len; + // // transform_cursor.next(); + // dbg!(transform_cursor.start(), transform_cursor.end()); + // } + // Some(Transform::Filter { .. }) => { + // push_filter(&mut new_transforms, summary); + // // transform_cursor.next(); + // } + // None => {} + // } } // For each hunk in the edit, push the non-hunk region preceding it, then @@ -329,32 +418,50 @@ impl FilterMap { ); } - // Set up the cursor for the next iteration by seeking it to the end of the edit - // and pushing the second half of any transform that's split thereby (we already - // covered the first half just above). - cursor.seek(&buffer_edit.old.end, Bias::Right); - let mut edit_old_end = cursor.end().1; - let mut edit_new_end = FilterOffset(new_transforms.summary().output.0.len); - if buffer_edit.old.end > cursor.start().0 { - let summary = self - .snapshot - .buffer_snapshot - .text_summary_for_range(buffer_edit.old.end..cursor.end().0); - match cursor.item() { + // transforms_cursor.start() + // v + // |----------------------------| old transforms + // --------------> <-------------> edits + // ^ buffer_edit.old.end + + transform_cursor.seek(&buffer_edit.old.end, Bias::Right); + let mut edit_old_end = transform_cursor.end().1; + let edit_new_end = FilterOffset(new_transforms.summary().output.0.len); + if buffer_edit.old.end > transform_cursor.start().0 { + // let summary = self + // .snapshot + // .buffer_snapshot + // .text_summary_for_range(buffer_edit.old.end..transform_cursor.end().0); + match transform_cursor.item() { Some(Transform::Isomorphic { .. }) => { - push_isomorphic(&mut new_transforms, summary); - edit_old_end.0 += buffer_edit.old.end - cursor.start().0; - edit_new_end.0 += buffer_edit.old.end - cursor.start().0; - cursor.next(); + // push_isomorphic(&mut new_transforms, summary); + edit_old_end.0 += buffer_edit.old.end - transform_cursor.start().0; + // edit_new_end.0 += buffer_edit.old.end - transform_cursor.start().0; + // transform_cursor.next(); } Some(Transform::Filter { .. }) => { - push_filter(&mut new_transforms, summary); - cursor.next(); + // push_filter(&mut new_transforms, summary); + // transform_cursor.next(); } None => {} } } + // If this is the last edit that intersects the current transform, consume the remainder of the transform and advance. + if buffer_edits.peek().is_none_or(|next_buffer_edit| { + next_buffer_edit.old.start >= transform_cursor.end().0 + }) { + let suffix_start = new_transforms.summary().input.0.len; + let suffix_len = transform_cursor.end().0 - buffer_edit.old.end; + dbg!(suffix_start, suffix_start + suffix_len); + dbg!(buffer_snapshot.text()); + let summary = buffer_snapshot.text_summary_for_range( + suffix_start..std::cmp::min(suffix_start + suffix_len, buffer_snapshot.len()), + ); + push_isomorphic(&mut new_transforms, summary); + transform_cursor.next(); + } + output_edits.push(text::Edit { old: edit_old_start..edit_old_end, new: edit_new_start..edit_new_end, @@ -362,9 +469,20 @@ impl FilterMap { } // Append old transforms after the last edit. - new_transforms.append(cursor.slice(&usize::MAX, Bias::Left), ()); - drop(cursor); + log::info!( + "input len before suffix is {}", + new_transforms.summary().input.0.len + ); + let suffix = transform_cursor.suffix(); + log::info!("suffix summary is {:?}", suffix.summary()); + new_transforms.append(suffix, ()); + log::info!( + "input len after suffix is {}", + new_transforms.summary().input.0.len + ); + + drop(transform_cursor); self.snapshot.transforms = new_transforms; self.snapshot.buffer_snapshot = buffer_snapshot; @@ -374,7 +492,41 @@ impl FilterMap { } } +impl FilterSnapshot { + #[cfg(any(test, feature = "test-support"))] + fn text(&self) -> String { + let mut offset = 0; + let mut output = String::new(); + + for transform in self.transforms.iter() { + match transform { + Transform::Isomorphic { summary } => { + let strs = self.buffer_snapshot.text_for_range( + offset.min(self.buffer_snapshot.len()) + ..(offset + summary.0.len).min(self.buffer_snapshot.len()), + ); + + for s in strs { + output.push_str(s); + } + + offset += summary.0.len; + } + Transform::Filter { summary } => { + offset += summary.0.len; + } + } + } + + output + } +} + fn push_isomorphic(transforms: &mut SumTree, summary_to_add: TextSummary) { + log::info!( + "push_isomorphic, input len after push is {}", + transforms.summary().input.0.len + summary_to_add.len + ); let mut merged = false; transforms.update_last( |transform| { @@ -396,6 +548,10 @@ fn push_isomorphic(transforms: &mut SumTree, summary_to_add: TextSumm } fn push_filter(transforms: &mut SumTree, summary_to_add: TextSummary) { + log::info!( + "push_filter, input len after push is {}", + transforms.summary().input.0.len + summary_to_add.len + ); let mut merged = false; transforms.update_last( |transform| { @@ -530,11 +686,10 @@ mod tests { rng.clone(), cx, ); - cx.run_until_parked(); - let buffer_edits = subscription.consume(); let buffer_snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx)); - filter_map.sync(buffer_snapshot, buffer_edits.edits().to_owned()); + let buffer_edits = subscription.consume().into_inner(); + filter_map.sync(buffer_snapshot, buffer_edits); } } } diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index f6ec7a9fbec754350eb46dafdc73cedc95d5aced..cd68aea24675f501c62bc4a03d3c0bcd7900002c 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -7192,8 +7192,8 @@ pub fn randomly_mutate_multibuffer_with_diffs( let buffer = buffers.choose(&mut rng).unwrap(); buffer.update(cx, |buf, cx| { let edit_count = rng.random_range(1..5); + log::info!("editing buffer"); buf.randomly_edit(&mut rng, edit_count, cx); - log::info!("buffer text:\n{}", buf.text()); *needs_diff_calculation = true; }); }