diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 7a225d6019edf8f09b1758d62e8181917649cc2b..f94b839cfc55e0ad0db8f623571c68234c609964 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -565,6 +565,15 @@ impl DisplayMap { self.inlay_map.current_inlays() } + // pub(crate) fn sync_unsplittable_groups(&mut self) { + // let group_boundaries = /* read diff hunk info from self */; + // self.splice_unsplittable_groups(group_boundaries); + // } + + // fn splice_unsplittable_groups(&mut self, group_boundaries: Vec) { + + // } + pub(crate) fn splice_inlays( &mut self, to_remove: &[InlayId], diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 99234899d3af7505c911355e34abe8f3fea3d0d2..b6b65be56ebb4299131b6d4fde6bdbd5f9c97159 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -507,6 +507,55 @@ impl BlockMap { BlockMapWriter(self) } + // current problem: how do we know how many spacer lines? + + // q: which buffer row if any does this wrap row end? log(n) + // iterate over buffer rows; for each one, translate into a wrap row and seek to that wrap row + // q: for a given buffer row, what is the wrap row count? log(n) + // q: which diff hunk if any does this wrap row end? + // q: for a given diff hunk, what is the wrap row count of the _other side_? + + // struct DiffLineMapping { + // group_boundaries: Vec, // group_boundaries[x] would be the start phys row for the `x`th group + // // group_boundaries[x + 1] would be the end phsy ... + // row_boundaries: Vec, // row_boundaries[x] would be the start wrapped row for the `x`th phys line + // // row_boundaries[x + 1] would be the end wrapped_row ... + // } + + // group is either: + // - a physical line that is not in a diff + // - an entire diff + // + // we never split up a group with a spacer + + // impl DiffLineMapping { + // // modulo off-by-one errors + // fn which_buffer_row_end(&self, wrap_row: usize) -> Option { + // self.row_boundaries.binary_search(wrap_row).ok() + // } + + // fn number_of_spacers_needed(&self, physical_line: usize, other: &DiffLineMapping) -> isize { + // (self.row_boundaries[physical_line + 1] - self.row_boundaries[physical_line]) - (other.row_boundaries[physical_line + 1] - other.row_boundaries[physical_line]) + // } + + // // left[x].end - left[x.start] - (right[x].end - right[x].start) + // } + + // - we get some wrap row edits + // - compute the affected range in the same way + // + // old: + // loop { + // find the next block in the affected region for the edit + // insert an isomorphic transform leading up to that block + // insert block + // } + // + // new: + // for each wrap row in the affected region { + // + // } + fn sync(&self, wrap_snapshot: &WrapSnapshot, mut edits: Patch) { let buffer = wrap_snapshot.buffer_snapshot(); diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index a31599ef9b276246226c12640fa8ffbec57eb9e3..e240511911cc74d17af0bef6fd6e86a5f564e4e8 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -107,6 +107,8 @@ impl FoldPoint { } pub fn to_offset(self, snapshot: &FoldSnapshot) -> FoldOffset { + dbg!(&self); + dbg!(snapshot.max_point()); let (start, _, item) = snapshot .transforms .find::, _>((), &self, Bias::Right); diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index e79e5555a61d0ddb8a93a1708c676554f191c3f6..32dc279fc44ec5df1862420d3e3f27f8b1a345fe 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -834,6 +834,25 @@ impl WrapSnapshot { None } + // Editor calls DisplayMap::sync() + // - that calls all ther other WhateverMap::sync()s + // + // BlockMap::sync() + // - WrapSnapshot + // - GroupBoundary (for the other Editor's DisplayMap) + // - the other Editor's BlockMap::sync() must have been called + // - which requires this Editor's BlockMap::sync() to have been called + // + // MultiBuffer::sync() <- InlayMap::sync() <- ... <- WrapMap::sync() (us) + // MultiBuffer::sync() <- InlayMap::sync() <- ... <- WrapMap::sync() (them) + // + // BlockMap::sync(our wrap map, their wrap map) (us) + // BlockMap::sync(their wrap map, our wrap map) (them) + + // pub fn next_group_boundary(&self, point: WrapPoint) -> Option<(u32, Option<(added_lines: u32, deleted_lines: u32)>)> { + // // ... + // } + #[cfg(test)] pub fn text(&self) -> String { self.text_chunks(0).collect() diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index bdfc7a7606a4d6e4a77a74ebb3c42a41449f002e..a33b0f04ba002b68891c15482f48d6a1cbeeaed9 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -209,6 +209,27 @@ struct BufferState { _subscriptions: [gpui::Subscription; 2], } +// struct DiffModeQuery(T); + +// struct InlaySnapshot { +// multibuffer: EditorMultibufferSnapshot, +// } + +// struct EditorMultibuffer { +// mode: DiffMode, +// multibuffer: Entity, +// } + +// trait ToPointGitAware { +// fn to_point(&self, snapshot: ..., mode: DiffMode) -> Point; +// } + +// impl ToPoint for (T, DiffMode) { +// fn to_point(&self, snapshot: ..., mode: DiffMode) -> (Point, DiffMode) { +// self.0.to_point(snapshot, self.1) +// } +// } + struct DiffState { diff: Entity, _subscription: gpui::Subscription, @@ -265,6 +286,7 @@ enum DiffTransform { base_text_byte_range: Range, has_trailing_newline: bool, }, + SkippedHunk(TextSummary), } #[derive(Clone, Copy, Debug)] @@ -486,6 +508,7 @@ impl<'a, D: TextDimension> Dimension<'a, DiffTransformSummary> for DiffTransform #[derive(Clone)] struct MultiBufferCursor<'a, D: TextDimension> { excerpts: Cursor<'a, 'static, Excerpt, ExcerptDimension>, + // DONE diff_transforms: Cursor<'a, 'static, DiffTransform, DiffTransforms>, diffs: &'a TreeMap, cached_region: Option>, @@ -2461,6 +2484,7 @@ impl MultiBuffer { } let mut excerpts = snapshot.excerpts.cursor::(()); + dbg!(snapshot.diff_transforms.iter().collect::>()); let mut old_diff_transforms = snapshot .diff_transforms .cursor::>(()); @@ -2471,6 +2495,7 @@ impl MultiBuffer { let mut at_transform_boundary = true; let mut end_of_current_insert = None; + dbg!("------------------", excerpt_edits.len()); let mut excerpt_edits = excerpt_edits.into_iter().peekable(); while let Some(edit) = excerpt_edits.next() { excerpts.seek_forward(&edit.new.start, Bias::Right); @@ -2510,10 +2535,17 @@ impl MultiBuffer { // Compute the end of the edit in output coordinates. let edit_old_end_overshoot = edit.old.end - old_diff_transforms.start().0; - let edit_new_end_overshoot = edit.new.end - new_diff_transforms.summary().excerpt_len(); + dbg!(new_diff_transforms.iter().collect::>()); + // text between new_diff_transforms.summary().excerpt_len() and edit.new.end is + // bone fide inserted, but it might be part of an added region. + let edit_new_end_overshoot = + dbg!(edit.new.end) - dbg!(new_diff_transforms.summary().excerpt_len()); let edit_old_end = old_diff_transforms.start().1 + edit_old_end_overshoot.value; - let edit_new_end = - new_diff_transforms.summary().output.len + edit_new_end_overshoot.value; + let mut edit_new_end = dbg!(new_diff_transforms.summary().output.len); + if true && end_of_current_insert.is_none() { + // FIXME + edit_new_end += dbg!(edit_new_end_overshoot.value); + } let output_edit = Edit { old: edit_old_start..edit_old_end, new: edit_new_start..edit_new_end, @@ -2532,6 +2564,7 @@ impl MultiBuffer { .peek() .is_none_or(|next_edit| next_edit.old.start >= old_diff_transforms.end().0) { + dbg!("SHOULD BE HERE"); let keep_next_old_transform = (old_diff_transforms.start().0 >= edit.old.end) && match old_diff_transforms.item() { Some(DiffTransform::BufferContent { @@ -2579,6 +2612,16 @@ impl MultiBuffer { snapshot.diff_transforms = new_diff_transforms; snapshot.edit_count += 1; + // FIXME + for edit in &output_edits { + dbg!(edit.new.end, snapshot.len()); + assert!( + edit.new.end <= snapshot.len(), + "transforms: {:?}", + snapshot.diff_transforms.iter().collect::>() + ) + } + #[cfg(any(test, feature = "test-support"))] snapshot.check_invariants(); output_edits @@ -2672,7 +2715,7 @@ impl MultiBuffer { Self::push_buffer_content_transform( snapshot, new_diff_transforms, - hunk_excerpt_start, + dbg!(hunk_excerpt_start), *end_of_current_insert, ); @@ -2732,8 +2775,11 @@ impl MultiBuffer { } if !hunk_buffer_range.is_empty() { + dbg!("yep"); *end_of_current_insert = Some((hunk_excerpt_end.min(excerpt_end), hunk_info)); + } else { + dbg!("nope"); } } } @@ -2801,8 +2847,10 @@ impl MultiBuffer { for (end_offset, inserted_hunk_info) in inserted_region.into_iter().chain(unchanged_region) { + dbg!(&inserted_hunk_info); let start_offset = new_transforms.summary().excerpt_len(); if end_offset <= start_offset { + dbg!(); continue; } let summary_to_add = old_snapshot @@ -2813,13 +2861,15 @@ impl MultiBuffer { inserted_hunk_info, summary_to_add, ) { - new_transforms.push( - DiffTransform::BufferContent { + let transform = if inserted_hunk_info.is_some() { + dbg!(DiffTransform::SkippedHunk(summary_to_add)) + } else { + dbg!(DiffTransform::BufferContent { summary: summary_to_add, inserted_hunk_info, - }, - (), - ) + }) + }; + new_transforms.push(transform, ()) } } } @@ -4091,6 +4141,7 @@ impl MultiBufferSnapshot { summary } + DiffTransform::SkippedHunk(_) => Default::default(), }; if range.end < diff_transform_end { return result; @@ -4129,6 +4180,7 @@ impl MultiBufferSnapshot { } suffix } + DiffTransform::SkippedHunk(_) => Default::default(), }; result.add_assign(&suffix); @@ -5838,10 +5890,46 @@ impl MultiBufferSnapshot { } } +fn next_non_skipped_diff_transform<'a, D: sum_tree::Dimension<'a, DiffTransformSummary>>( + cursor: &mut Cursor<'a, 'static, DiffTransform, D>, +) { + cursor.next(); + if let Some(item) = cursor.item() + && item.is_skipped_hunk() + { + cursor.next(); + } + // Can't have consecutive skipped hunks +} + +fn prev_non_skipped_diff_transform<'a, D: sum_tree::Dimension<'a, DiffTransformSummary>>( + cursor: &mut Cursor<'a, 'static, DiffTransform, D>, +) { + cursor.prev(); + if let Some(item) = cursor.item() + && item.is_skipped_hunk() + { + cursor.prev(); + } + // Can't have consecutive skipped hunks +} + impl<'a, D> MultiBufferCursor<'a, D> where D: TextDimension + Ord + Sub, { + // fn seek_forward_past_skipped_hunks(&mut self) { + // while let Some(DiffTransform::SkippedHunk(_)) = self.diff_transforms.item() { + // self.diff_transforms.next(); + // } + // } + + // fn seek_backward_past_skipped_hunks(&mut self) { + // while let Some(DiffTransform::SkippedHunk(_)) = self.diff_transforms.item() { + // self.diff_transforms.prev(); + // } + // } + fn seek(&mut self, position: &D) { self.cached_region.take(); self.diff_transforms @@ -5849,7 +5937,7 @@ where if self.diff_transforms.item().is_none() && *position == self.diff_transforms.start().output_dimension.0 { - self.diff_transforms.prev(); + prev_non_skipped_diff_transform(&mut self.diff_transforms); } let mut excerpt_position = self.diff_transforms.start().excerpt_dimension.0; @@ -5872,7 +5960,7 @@ where if self.diff_transforms.item().is_none() && *position == self.diff_transforms.start().output_dimension.0 { - self.diff_transforms.prev(); + prev_non_skipped_diff_transform(&mut self.diff_transforms); } let overshoot = *position - self.diff_transforms.start().output_dimension.0; @@ -5905,7 +5993,8 @@ where && self.diff_transforms.start().excerpt_dimension < *self.excerpts.start() && self.diff_transforms.next_item().is_some() { - self.diff_transforms.next(); + // FIXME by the excerpt dimension comparison, there must be a non-skipped diff transform to find + next_non_skipped_diff_transform(&mut self.diff_transforms); } } @@ -5917,10 +6006,12 @@ where .excerpt_dimension .cmp(&self.excerpts.end()) { - cmp::Ordering::Less => self.diff_transforms.next(), + cmp::Ordering::Less => { + next_non_skipped_diff_transform(&mut self.diff_transforms); + } cmp::Ordering::Greater => self.excerpts.next(), cmp::Ordering::Equal => { - self.diff_transforms.next(); + next_non_skipped_diff_transform(&mut self.diff_transforms); if self.diff_transforms.end().excerpt_dimension > self.excerpts.end() || self.diff_transforms.item().is_none() { @@ -5947,9 +6038,11 @@ where .cmp(self.excerpts.start()) { cmp::Ordering::Less => self.excerpts.prev(), - cmp::Ordering::Greater => self.diff_transforms.prev(), + cmp::Ordering::Greater => { + next_non_skipped_diff_transform(&mut self.diff_transforms); + } cmp::Ordering::Equal => { - self.diff_transforms.prev(); + prev_non_skipped_diff_transform(&mut self.diff_transforms); if self.diff_transforms.start().excerpt_dimension < *self.excerpts.start() || self.diff_transforms.item().is_none() { @@ -5978,7 +6071,10 @@ where self.diff_transforms.next(); prev_transform.is_none_or(|next_transform| { - matches!(next_transform, DiffTransform::BufferContent { .. }) + matches!( + next_transform, + DiffTransform::BufferContent { .. } | DiffTransform::SkippedHunk(_) + ) }) } @@ -5993,7 +6089,7 @@ where let next_transform = self.diff_transforms.next_item(); next_transform.is_none_or(|next_transform| match next_transform { - DiffTransform::BufferContent { .. } => true, + DiffTransform::BufferContent { .. } | DiffTransform::SkippedHunk(_) => true, DiffTransform::DeletedHunk { hunk_info, .. } => self .excerpts .item() @@ -6090,6 +6186,10 @@ where range: start..end, }) } + _ => { + log::error!("cursor parked on skipped hunk"); + None + } } } @@ -6397,6 +6497,14 @@ impl DiffTransform { DiffTransform::BufferContent { inserted_hunk_info, .. } => *inserted_hunk_info, + DiffTransform::SkippedHunk(_) => None, + } + } + + fn is_skipped_hunk(&self) -> bool { + match self { + DiffTransform::SkippedHunk(_) => true, + _ => false, } } } @@ -6405,6 +6513,10 @@ impl sum_tree::Item for DiffTransform { type Summary = DiffTransformSummary; fn summary(&self, _: ::Context<'_>) -> Self::Summary { + // in "no deletions" mode, + // DiffTransform::DeletedHunk doesn't add to output either + // in "no additions" mode, + // DiffTransform::BufferContent doesn't add to output if it's an insertion match self { DiffTransform::BufferContent { summary, .. } => DiffTransformSummary { input: *summary, @@ -6414,6 +6526,10 @@ impl sum_tree::Item for DiffTransform { input: TextSummary::default(), output: *summary, }, + DiffTransform::SkippedHunk(summary) => DiffTransformSummary { + input: *summary, + output: TextSummary::default(), + }, } } } @@ -6868,83 +6984,87 @@ impl<'a> Iterator for MultiBufferChunks<'a> { let diff_transform_end = self.diff_transforms.end().0; debug_assert!(self.range.start < diff_transform_end); - let diff_transform = self.diff_transforms.item()?; - match diff_transform { - DiffTransform::BufferContent { .. } => { - let chunk = if let Some(chunk) = &mut self.buffer_chunk { - chunk - } else { - let chunk = self.next_excerpt_chunk().unwrap(); - self.buffer_chunk.insert(chunk) - }; + loop { + let diff_transform = self.diff_transforms.item()?; + match diff_transform { + DiffTransform::BufferContent { .. } => { + let chunk = if let Some(chunk) = &mut self.buffer_chunk { + chunk + } else { + let chunk = self.next_excerpt_chunk().unwrap(); + self.buffer_chunk.insert(chunk) + }; - let chunk_end = self.range.start + chunk.text.len(); - let diff_transform_end = diff_transform_end.min(self.range.end); - - if diff_transform_end < chunk_end { - let split_idx = diff_transform_end - self.range.start; - let (before, after) = chunk.text.split_at(split_idx); - self.range.start = diff_transform_end; - let mask = 1u128.unbounded_shl(split_idx as u32).wrapping_sub(1); - let chars = chunk.chars & mask; - let tabs = chunk.tabs & mask; - - chunk.text = after; - chunk.chars = chunk.chars >> split_idx; - chunk.tabs = chunk.tabs >> split_idx; - - Some(Chunk { - text: before, - chars, - tabs, - ..chunk.clone() - }) - } else { - self.range.start = chunk_end; - self.buffer_chunk.take() + let chunk_end = self.range.start + chunk.text.len(); + let diff_transform_end = diff_transform_end.min(self.range.end); + + if diff_transform_end < chunk_end { + let split_idx = diff_transform_end - self.range.start; + let (before, after) = chunk.text.split_at(split_idx); + self.range.start = diff_transform_end; + let mask = 1u128.unbounded_shl(split_idx as u32).wrapping_sub(1); + let chars = chunk.chars & mask; + let tabs = chunk.tabs & mask; + + chunk.text = after; + chunk.chars = chunk.chars >> split_idx; + chunk.tabs = chunk.tabs >> split_idx; + + break Some(Chunk { + text: before, + chars, + tabs, + ..chunk.clone() + }); + } else { + self.range.start = chunk_end; + break self.buffer_chunk.take(); + } } - } - DiffTransform::DeletedHunk { - buffer_id, - base_text_byte_range, - has_trailing_newline, - .. - } => { - let base_text_start = - base_text_byte_range.start + self.range.start - diff_transform_start; - let base_text_end = - base_text_byte_range.start + self.range.end - diff_transform_start; - let base_text_end = base_text_end.min(base_text_byte_range.end); - - let mut chunks = if let Some((_, mut chunks)) = self - .diff_base_chunks - .take() - .filter(|(id, _)| id == buffer_id) - { - if chunks.range().start != base_text_start || chunks.range().end < base_text_end + DiffTransform::DeletedHunk { + buffer_id, + base_text_byte_range, + has_trailing_newline, + .. + } => { + let base_text_start = + base_text_byte_range.start + self.range.start - diff_transform_start; + let base_text_end = + base_text_byte_range.start + self.range.end - diff_transform_start; + let base_text_end = base_text_end.min(base_text_byte_range.end); + + let mut chunks = if let Some((_, mut chunks)) = self + .diff_base_chunks + .take() + .filter(|(id, _)| id == buffer_id) { - chunks.seek(base_text_start..base_text_end); - } - chunks - } else { - let base_buffer = &self.diffs.get(buffer_id)?.base_text(); - base_buffer.chunks(base_text_start..base_text_end, self.language_aware) - }; + if chunks.range().start != base_text_start + || chunks.range().end < base_text_end + { + chunks.seek(base_text_start..base_text_end); + } + chunks + } else { + let base_buffer = &self.diffs.get(buffer_id)?.base_text(); + base_buffer.chunks(base_text_start..base_text_end, self.language_aware) + }; - let chunk = if let Some(chunk) = chunks.next() { - self.range.start += chunk.text.len(); - self.diff_base_chunks = Some((*buffer_id, chunks)); - chunk - } else { - debug_assert!(has_trailing_newline); - self.range.start += "\n".len(); - Chunk { - text: "\n", - chars: 1u128, - ..Default::default() - } - }; - Some(chunk) + let chunk = if let Some(chunk) = chunks.next() { + self.range.start += chunk.text.len(); + self.diff_base_chunks = Some((*buffer_id, chunks)); + chunk + } else { + debug_assert!(has_trailing_newline); + self.range.start += "\n".len(); + Chunk { + text: "\n", + chars: 1u128, + ..Default::default() + } + }; + break Some(chunk); + } + DiffTransform::SkippedHunk(_) => self.diff_transforms.next(), } } } diff --git a/crates/multi_buffer/src/multi_buffer_tests.rs b/crates/multi_buffer/src/multi_buffer_tests.rs index a9121b9104400d88d5f22801db1bfebaeeb060d6..3de8571720c953067a370c39ea502436ee858d4f 100644 --- a/crates/multi_buffer/src/multi_buffer_tests.rs +++ b/crates/multi_buffer/src/multi_buffer_tests.rs @@ -1377,6 +1377,45 @@ fn test_basic_diff_hunks(cx: &mut TestAppContext) { ); } +#[gpui::test] +async fn test_insertion(cx: &mut TestAppContext) { + let text = indoc!( + " + a + b + c + " + ); + let base_text = ""; + let buffer = cx.new(|cx| Buffer::local(text, cx)); + let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx)); + cx.run_until_parked(); + + let multibuffer = cx.new(|cx| { + let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx); + multibuffer.add_diff(diff.clone(), cx); + multibuffer + }); + + multibuffer.update(cx, |multibuffer, cx| { + multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx); + }); + cx.run_until_parked(); + let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| { + (multibuffer.snapshot(cx), multibuffer.subscribe()) + }); + assert_eq!( + snapshot.text(), + indoc!( + " + a + b + c + " + ), + ); +} + #[gpui::test] fn test_repeatedly_expand_a_diff_hunk(cx: &mut TestAppContext) { let text = indoc!( @@ -3962,3 +4001,28 @@ fn test_random_chunk_bitmaps_with_diffs(cx: &mut App, mut rng: StdRng) { } } } + +#[gpui::test] +async fn test_seeking_with_skipped_hunks() { + let first_part = "one\n"; + let transforms = SumTree::from_iter( + [ + DiffTransform::BufferContent { + summary: TextSummary::from(first_part), + inserted_hunk_info: None, + }, + DiffTransform::SkippedHunk(TextSummary::from("2\n")), + DiffTransform::SkippedHunk(TextSummary::from("22\n")), + DiffTransform::BufferContent { + summary: TextSummary::from("3!!!\n"), + inserted_hunk_info: None, + }, + ], + (), + ); + let mut cursor = transforms.cursor::(()); + cursor.seek(&first_part.len(), Bias::Left); + dbg!(cursor.item()); + cursor.seek(&first_part.len(), Bias::Right); + dbg!(cursor.item()); +} diff --git a/crates/project/src/git_store/conflict_set.rs b/crates/project/src/git_store/conflict_set.rs index 160a384a4a0ff4481c97b6eda75faded28f01624..c7caf5a56759e6e6f54ba4834a28a67216881b82 100644 --- a/crates/project/src/git_store/conflict_set.rs +++ b/crates/project/src/git_store/conflict_set.rs @@ -169,6 +169,16 @@ impl ConflictSet { cx.emit(update); } + // Vec<(Range)> + // Vec<(Range, &str)> + // + // [(1..2, ""), (6..7, "")] + // {"hello": "world"} + // {hello: "world"} + // + // foo(bar); + // } + pub fn parse(buffer: &text::BufferSnapshot) -> ConflictSetSnapshot { let mut conflicts = Vec::new();