diff --git a/crates/buffer_diff/src/buffer_diff.rs b/crates/buffer_diff/src/buffer_diff.rs index 63df9b3cd3277b0493a07fe6c2414c5f3c777a71..5919770c61397d1b275ae3fb970887f8dee24dd0 100644 --- a/crates/buffer_diff/src/buffer_diff.rs +++ b/crates/buffer_diff/src/buffer_diff.rs @@ -6,42 +6,19 @@ use language::{ language_settings::language_settings, word_diff_ranges, }; use rope::Rope; -use std::{cmp::Ordering, future::Future, iter, ops::Range, sync::Arc}; +use std::{ + cmp::Ordering, + future::Future, + iter, + ops::{Range, RangeInclusive}, + sync::Arc, +}; use sum_tree::SumTree; use text::{ Anchor, Bias, BufferId, Edit, OffsetRangeExt, Patch, Point, ToOffset as _, ToPoint as _, }; use util::ResultExt; -fn translate_point_through_patch( - patch: &Patch, - point: Point, -) -> (Range, Range) { - let edits = patch.edits(); - - let ix = match edits.binary_search_by(|probe| probe.old.start.cmp(&point)) { - Ok(ix) => ix, - Err(ix) => { - if ix == 0 { - return (point..point, point..point); - } else { - ix - 1 - } - } - }; - - if let Some(edit) = edits.get(ix) { - if point > edit.old.end { - let translated = edit.new.end + (point - edit.old.end); - (translated..translated, point..point) - } else { - (edit.new.start..edit.new.end, edit.old.start..edit.old.end) - } - } else { - (point..point, point..point) - } -} - pub const MAX_WORD_DIFF_LINE_COUNT: usize = 5; pub struct BufferDiff { @@ -430,15 +407,15 @@ impl BufferDiffSnapshot { result } - pub fn points_to_base_text_points<'a>( + /// Returns a patch mapping the provided main buffer snapshot to the base text of this diff. + /// + /// The returned patch is guaranteed to be accurate for all main buffer points in the provided range, + /// but not necessarily for points outside that range. + pub fn patch_for_buffer_range<'a>( &'a self, - points: impl IntoIterator + 'a, + _range: RangeInclusive, buffer: &'a text::BufferSnapshot, - ) -> ( - impl 'a + Iterator>, - Option>, - Option<(Point, Range)>, - ) { + ) -> Patch { let original_snapshot = self.original_buffer_snapshot(); let edits_since: Vec> = buffer @@ -447,7 +424,7 @@ impl BufferDiffSnapshot { let mut inverted_edits_since = Patch::new(edits_since); inverted_edits_since.invert(); - let composed = inverted_edits_since.compose( + inverted_edits_since.compose( self.inner .hunks .iter() @@ -475,42 +452,18 @@ impl BufferDiffSnapshot { None }, ), - ); - - let mut points = points.into_iter().peekable(); - - let first_group = points.peek().map(|point| { - let (_, old_range) = translate_point_through_patch(&composed, *point); - old_range - }); - - let prev_boundary = points.peek().and_then(|first_point| { - if first_point.row > 0 { - let prev_point = Point::new(first_point.row - 1, 0); - let (range, _) = translate_point_through_patch(&composed, prev_point); - Some((prev_point, range)) - } else { - None - } - }); - - let iter = points.map(move |point| { - let (range, _) = translate_point_through_patch(&composed, point); - range - }); - - (iter, first_group, prev_boundary) + ) } - pub fn base_text_points_to_points<'a>( + /// Returns a patch mapping the base text of this diff to the provided buffer snapshot. + /// + /// The returned patch is guaranteed to be accurate for all base text points in the provided range, + /// but not necessarily for points outside that range. + pub fn patch_for_base_text_range<'a>( &'a self, - points: impl IntoIterator + 'a, + _range: RangeInclusive, buffer: &'a text::BufferSnapshot, - ) -> ( - impl 'a + Iterator>, - Option>, - Option<(Point, Range)>, - ) { + ) -> Patch { let original_snapshot = self.original_buffer_snapshot(); let mut hunk_edits: Vec> = Vec::new(); @@ -536,31 +489,55 @@ impl BufferDiffSnapshot { } let hunk_patch = Patch::new(hunk_edits); - let composed = hunk_patch.compose(buffer.edits_since::(original_snapshot.version())); - - let mut points = points.into_iter().peekable(); + hunk_patch.compose(buffer.edits_since::(original_snapshot.version())) + } - let first_group = points.peek().map(|point| { - let (_, result) = translate_point_through_patch(&composed, *point); - result - }); + pub fn buffer_point_to_base_text_range( + &self, + point: Point, + buffer: &text::BufferSnapshot, + ) -> Range { + let patch = self.patch_for_buffer_range(point..=point, buffer); + let edit = patch.edit_for_old_position(point); + edit.new + } - let prev_boundary = points.peek().and_then(|first_point| { - if first_point.row > 0 { - let prev_point = Point::new(first_point.row - 1, 0); - let (range, _) = translate_point_through_patch(&composed, prev_point); - Some((prev_point, range)) - } else { - None - } - }); + pub fn base_text_point_to_buffer_range( + &self, + point: Point, + buffer: &text::BufferSnapshot, + ) -> Range { + let patch = self.patch_for_base_text_range(point..=point, buffer); + let edit = patch.edit_for_old_position(point); + edit.new + } - let iter = points.map(move |point| { - let (range, _) = translate_point_through_patch(&composed, point); - range - }); + pub fn buffer_point_to_base_text_point( + &self, + point: Point, + buffer: &text::BufferSnapshot, + ) -> Point { + let patch = self.patch_for_buffer_range(point..=point, buffer); + let edit = patch.edit_for_old_position(point); + if point == edit.old.end { + edit.new.end + } else { + edit.new.start + } + } - (iter, first_group, prev_boundary) + pub fn base_text_point_to_buffer_point( + &self, + point: Point, + buffer: &text::BufferSnapshot, + ) -> Point { + let patch = self.patch_for_base_text_range(point..=point, buffer); + let edit = patch.edit_for_old_position(point); + if point == edit.old.end { + edit.new.end + } else { + edit.new.start + } } } @@ -3190,1291 +3167,4 @@ mod tests { "extended_range should equal changed_range when edit is within the hunk" ); } - - fn assert_rows_to_base_text_rows_visual( - buffer: &Entity, - diff: &Entity, - source_text: &str, - annotated_target: &str, - cx: &mut gpui::TestAppContext, - ) { - let (target_text, expected_ranges) = parse_row_annotations(annotated_target); - - let buffer = buffer.read_with(cx, |buffer, _| buffer.text_snapshot()); - let diff = diff.update(cx, |diff, cx| diff.snapshot(cx)); - - assert_eq!( - buffer.text(), - source_text, - "buffer text does not match source text" - ); - - assert_eq!( - diff.base_text_string().unwrap_or_default(), - target_text, - "base text does not match stripped annotated target" - ); - - let num_rows = source_text.lines().count() as u32; - let max_point = buffer.max_point(); - let points = (0..=num_rows).map(move |row| { - if row == num_rows && max_point.column > 0 { - max_point - } else { - Point::new(row, 0) - } - }); - let actual_ranges: Vec<_> = diff.points_to_base_text_points(points, &buffer).0.collect(); - - assert_eq!( - actual_ranges, expected_ranges, - "\nsource (buffer):\n{}\ntarget (base):\n{}\nexpected: {:?}\nactual: {:?}", - source_text, target_text, expected_ranges, actual_ranges - ); - } - - fn assert_base_text_rows_to_rows_visual( - buffer: &Entity, - diff: &Entity, - source_text: &str, - annotated_target: &str, - cx: &mut gpui::TestAppContext, - ) { - let (target_text, expected_ranges) = parse_row_annotations(annotated_target); - - let buffer = buffer.read_with(cx, |buffer, _| buffer.text_snapshot()); - let diff = diff.update(cx, |diff, cx| diff.snapshot(cx)); - - assert_eq!( - diff.base_text_string().unwrap_or_default(), - source_text, - "base text does not match source text" - ); - - assert_eq!( - buffer.text(), - target_text, - "buffer text does not match stripped annotated target" - ); - - let num_rows = source_text.lines().count() as u32; - let base_max_point = diff.base_text().max_point(); - let points = (0..=num_rows).map(move |row| { - if row == num_rows && base_max_point.column > 0 { - base_max_point - } else { - Point::new(row, 0) - } - }); - let actual_ranges: Vec<_> = diff.base_text_points_to_points(points, &buffer).0.collect(); - - assert_eq!( - actual_ranges, expected_ranges, - "\nsource (base):\n{}\ntarget (buffer):\n{}\nexpected: {:?}\nactual: {:?}", - source_text, target_text, expected_ranges, actual_ranges - ); - } - - fn parse_row_annotations(annotated_text: &str) -> (String, Vec>) { - let mut starts: std::collections::HashMap = std::collections::HashMap::new(); - let mut ends: std::collections::HashMap = std::collections::HashMap::new(); - - let mut clean_text = String::new(); - let mut current_point = Point::new(0, 0); - let mut chars = annotated_text.chars().peekable(); - - while let Some(c) = chars.next() { - if c == '<' { - let mut num_str = String::new(); - while let Some(&next) = chars.peek() { - if next.is_ascii_digit() { - num_str.push(chars.next().unwrap()); - } else { - break; - } - } - if !num_str.is_empty() { - let row_num: u32 = num_str.parse().unwrap(); - starts.insert(row_num, current_point); - - if chars.peek() == Some(&'>') { - chars.next(); - ends.insert(row_num, current_point); - } - } else { - clean_text.push(c); - current_point.column += 1; - } - } else if c.is_ascii_digit() { - let mut num_str = String::from(c); - while let Some(&next) = chars.peek() { - if next.is_ascii_digit() { - num_str.push(chars.next().unwrap()); - } else { - break; - } - } - if chars.peek() == Some(&'>') { - chars.next(); - let row_num: u32 = num_str.parse().unwrap(); - ends.insert(row_num, current_point); - } else { - for ch in num_str.chars() { - clean_text.push(ch); - current_point.column += 1; - } - } - } else if c == '\n' { - clean_text.push(c); - current_point.row += 1; - current_point.column = 0; - } else { - clean_text.push(c); - current_point.column += 1; - } - } - - let max_row = starts.keys().chain(ends.keys()).max().copied().unwrap_or(0); - let mut ranges: Vec> = Vec::new(); - for row in 0..=max_row { - let start = starts.get(&row).copied().unwrap_or(Point::new(0, 0)); - let end = ends.get(&row).copied().unwrap_or(start); - ranges.push(start..end); - } - - (clean_text, ranges) - } - - fn make_diff( - base_text: &str, - buffer_text: &str, - cx: &mut gpui::TestAppContext, - ) -> (Entity, Entity) { - let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx)); - let diff = cx.new(|cx| { - BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx) - }); - (buffer, diff) - } - - #[gpui::test] - async fn test_row_translation_visual(cx: &mut gpui::TestAppContext) { - use unindent::Unindent; - - { - let buffer_text = " - aaa - bbb - ccc - " - .unindent(); - let annotated_base = " - <0>aaa - <1>bbb - <2>ccc - <3>" - .unindent(); - let (base_text, _) = parse_row_annotations(&annotated_base); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx); - } - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let annotated_buffer = " - <0>aaa - <1>bbb - <2>ccc - <3>" - .unindent(); - let (buffer_text, _) = parse_row_annotations(&annotated_buffer); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx); - } - - { - let buffer_text = " - XXX - bbb - ccc - " - .unindent(); - let annotated_base = " - <0<1aaa - 0>1>bbb - <2>ccc - <3>" - .unindent(); - let (base_text, _) = parse_row_annotations(&annotated_base); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx); - } - - { - let buffer_text = " - aaa - NEW - ccc - " - .unindent(); - let annotated_base = " - <0>aaa - <1><2>ccc - <3>" - .unindent(); - let (base_text, _) = parse_row_annotations(&annotated_base); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx); - } - - { - let base_text = " - aaa - ccc - " - .unindent(); - let annotated_buffer = " - <0>aaa - <1NEW - 1>ccc - <2>" - .unindent(); - let (buffer_text, _) = parse_row_annotations(&annotated_buffer); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx); - } - - { - let buffer_text = "aaa\nbbb"; - let annotated_base = "<0>aaa\n<1>bbb<2>"; - let (base_text, _) = parse_row_annotations(annotated_base); - let (buffer, diff) = make_diff(&base_text, buffer_text, cx); - assert_rows_to_base_text_rows_visual(&buffer, &diff, buffer_text, annotated_base, cx); - assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, annotated_base, cx); - } - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let annotated_buffer = " - <0<1XXX - 0>1>bbb - <2>ccc - <3>" - .unindent(); - let (buffer_text, _) = parse_row_annotations(&annotated_buffer); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx); - } - - { - let buffer_text = " - aaa - bbb - XXX - " - .unindent(); - let annotated_base = " - <0>aaa - <1>bbb - <2<3ccc - 2>3>" - .unindent(); - let (base_text, _) = parse_row_annotations(&annotated_base); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx); - } - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let annotated_buffer = " - <0>aaa - <1>bbb - <2<3XXX - 2>3>" - .unindent(); - let (buffer_text, _) = parse_row_annotations(&annotated_buffer); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx); - } - - { - let buffer_text = " - aaa - ccc - " - .unindent(); - let annotated_base = " - <0>aaa - <1DELETED - 1>ccc - <2>" - .unindent(); - let (base_text, _) = parse_row_annotations(&annotated_base); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx); - } - - { - let base_text = " - aaa - DELETED - ccc - " - .unindent(); - let annotated_buffer = " - <0>aaa - <1><2>ccc - <3>" - .unindent(); - let (buffer_text, _) = parse_row_annotations(&annotated_buffer); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx); - } - } - - #[gpui::test] - async fn test_row_translation_with_edits_since_diff(cx: &mut gpui::TestAppContext) { - use unindent::Unindent; - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let buffer_text = base_text.clone(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(4..7, "XXX")], None, cx); - }); - - let new_buffer_text = " - aaa - XXX - ccc - " - .unindent(); - let annotated_base = " - <0>aaa - <1bbb1> - <2>ccc - <3>" - .unindent(); - assert_rows_to_base_text_rows_visual( - &buffer, - &diff, - &new_buffer_text, - &annotated_base, - cx, - ); - } - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let buffer_text = base_text.clone(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(4..7, "XXX")], None, cx); - }); - - let annotated_buffer = " - <0>aaa - <1XXX1> - <2>ccc - <3>" - .unindent(); - assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx); - } - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let buffer_text = base_text.clone(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(4..4, "NEW\n")], None, cx); - }); - - let new_buffer_text = " - aaa - NEW - bbb - ccc - " - .unindent(); - let annotated_base = " - <0>aaa - <1><2>bbb - <3>ccc - <4>" - .unindent(); - assert_rows_to_base_text_rows_visual( - &buffer, - &diff, - &new_buffer_text, - &annotated_base, - cx, - ); - } - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let buffer_text = base_text.clone(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(4..4, "NEW\n")], None, cx); - }); - - let annotated_buffer = " - <0>aaa - <1NEW - 1>bbb - <2>ccc - <3>" - .unindent(); - assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx); - } - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let buffer_text = base_text.clone(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(4..8, "")], None, cx); - }); - - let new_buffer_text = " - aaa - ccc - " - .unindent(); - let annotated_base = " - <0>aaa - <1bbb - 1>ccc - <2>" - .unindent(); - assert_rows_to_base_text_rows_visual( - &buffer, - &diff, - &new_buffer_text, - &annotated_base, - cx, - ); - } - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let buffer_text = base_text.clone(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(4..8, "")], None, cx); - }); - - let annotated_buffer = " - <0>aaa - <1><2>ccc - <3>" - .unindent(); - assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx); - } - - { - let base_text = " - aaa - bbb - ccc - ddd - eee - " - .unindent(); - let buffer_text = " - aaa - XXX - ccc - ddd - eee - " - .unindent(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(12..15, "YYY")], None, cx); - }); - - let new_buffer_text = " - aaa - XXX - ccc - YYY - eee - " - .unindent(); - let annotated_base = " - <0>aaa - <1<2bbb - 1>2>ccc - <3ddd3> - <4>eee - <5>" - .unindent(); - assert_rows_to_base_text_rows_visual( - &buffer, - &diff, - &new_buffer_text, - &annotated_base, - cx, - ); - } - - { - let base_text = " - aaa - bbb - ccc - ddd - eee - " - .unindent(); - let buffer_text = " - aaa - XXX - ccc - ddd - eee - " - .unindent(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(12..15, "YYY")], None, cx); - }); - - let annotated_buffer = " - <0>aaa - <1<2XXX - 1>2>ccc - <3YYY3> - <4>eee - <5>" - .unindent(); - assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx); - } - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let buffer_text = base_text.clone(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(0..0, "NEW\n")], None, cx); - }); - - let new_buffer_text = " - NEW - aaa - bbb - ccc - " - .unindent(); - let annotated_base = " - <0><1>aaa - <2>bbb - <3>ccc - <4>" - .unindent(); - assert_rows_to_base_text_rows_visual( - &buffer, - &diff, - &new_buffer_text, - &annotated_base, - cx, - ); - } - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let buffer_text = base_text.clone(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(0..0, "NEW\n")], None, cx); - }); - - let annotated_buffer = " - <0NEW - 0>aaa - <1>bbb - <2>ccc - <3>" - .unindent(); - assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx); - } - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let buffer_text = base_text.clone(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(12..12, "NEW\n")], None, cx); - }); - - let new_buffer_text = " - aaa - bbb - ccc - NEW - " - .unindent(); - let annotated_base = " - <0>aaa - <1>bbb - <2>ccc - <3><4>" - .unindent(); - assert_rows_to_base_text_rows_visual( - &buffer, - &diff, - &new_buffer_text, - &annotated_base, - cx, - ); - } - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let buffer_text = base_text.clone(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(12..12, "NEW\n")], None, cx); - }); - - let annotated_buffer = " - <0>aaa - <1>bbb - <2>ccc - <3NEW - 3>" - .unindent(); - assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx); - } - - { - let base_text = ""; - let buffer_text = "aaa\n"; - let (buffer, diff) = make_diff(base_text, buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(4..4, "bbb\n")], None, cx); - }); - - let new_buffer_text = " - aaa - bbb - " - .unindent(); - let annotated_base = "<0><1><2>"; - assert_rows_to_base_text_rows_visual( - &buffer, - &diff, - &new_buffer_text, - &annotated_base, - cx, - ); - } - - { - let base_text = "aaa\n"; - let buffer_text = ""; - let (buffer, diff) = make_diff(base_text, buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(0..0, "bbb\n")], None, cx); - }); - - let new_buffer_text = "bbb\n"; - let annotated_base = " - <0<1aaa - 0>1>" - .unindent(); - assert_rows_to_base_text_rows_visual( - &buffer, - &diff, - &new_buffer_text, - &annotated_base, - cx, - ); - } - - { - let base_text = ""; - let buffer_text = ""; - let (buffer, diff) = make_diff(base_text, buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(0..0, "aaa\n")], None, cx); - }); - - let new_buffer_text = "aaa\n"; - let annotated_base = "<0><1>"; - assert_rows_to_base_text_rows_visual( - &buffer, - &diff, - &new_buffer_text, - &annotated_base, - cx, - ); - } - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let buffer_text = " - aaa - XXX - ccc - " - .unindent(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(4..7, "YYY")], None, cx); - }); - - let new_buffer_text = " - aaa - YYY - ccc - " - .unindent(); - let annotated_base = " - <0>aaa - <1<2bbb - 1>2>ccc - <3>" - .unindent(); - assert_rows_to_base_text_rows_visual( - &buffer, - &diff, - &new_buffer_text, - &annotated_base, - cx, - ); - } - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let buffer_text = " - aaa - XXX - ccc - " - .unindent(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(4..7, "YYY")], None, cx); - }); - - let annotated_buffer = " - <0>aaa - <1<2YYY - 1>2>ccc - <3>" - .unindent(); - assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx); - } - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let buffer_text = " - aaa - XXXX - ccc - " - .unindent(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(4..6, "YY")], None, cx); - }); - - let new_buffer_text = " - aaa - YYXX - ccc - " - .unindent(); - let annotated_base = " - <0>aaa - <1<2bbb - 1>2>ccc - <3>" - .unindent(); - assert_rows_to_base_text_rows_visual( - &buffer, - &diff, - &new_buffer_text, - &annotated_base, - cx, - ); - } - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let buffer_text = " - aaa - XXXX - ccc - " - .unindent(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(6..8, "YY")], None, cx); - }); - - let new_buffer_text = " - aaa - XXYY - ccc - " - .unindent(); - let annotated_base = " - <0>aaa - <1<2bbb - 1>2>ccc - <3>" - .unindent(); - assert_rows_to_base_text_rows_visual( - &buffer, - &diff, - &new_buffer_text, - &annotated_base, - cx, - ); - } - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let buffer_text = " - aaa - XXX - ccc - " - .unindent(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(4..4, "NEW")], None, cx); - }); - - let new_buffer_text = " - aaa - NEWXXX - ccc - " - .unindent(); - let annotated_base = " - <0>aaa - <1<2bbb - 1>2>ccc - <3>" - .unindent(); - assert_rows_to_base_text_rows_visual( - &buffer, - &diff, - &new_buffer_text, - &annotated_base, - cx, - ); - } - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let buffer_text = " - aaa - XXX - ccc - " - .unindent(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(7..7, "NEW")], None, cx); - }); - - let new_buffer_text = " - aaa - XXXNEW - ccc - " - .unindent(); - let annotated_base = " - <0>aaa - <1<2bbb - 1>2>ccc - <3>" - .unindent(); - assert_rows_to_base_text_rows_visual( - &buffer, - &diff, - &new_buffer_text, - &annotated_base, - cx, - ); - } - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let buffer_text = " - aaa - ccc - " - .unindent(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(4..4, "NEW\n")], None, cx); - }); - - let new_buffer_text = " - aaa - NEW - ccc - " - .unindent(); - let annotated_base = " - <0>aaa - <1<2bbb - 1>2>ccc - <3>" - .unindent(); - assert_rows_to_base_text_rows_visual( - &buffer, - &diff, - &new_buffer_text, - &annotated_base, - cx, - ); - } - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let buffer_text = " - aaa - ccc - " - .unindent(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(4..4, "NEW\n")], None, cx); - }); - - let annotated_buffer = " - <0>aaa - <1<2NEW - 1>2>ccc - <3>" - .unindent(); - assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx); - } - - { - let base_text = " - aaa - bbb - ccc - ddd - " - .unindent(); - let buffer_text = " - aaa - ddd - " - .unindent(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(4..4, "XXX\nYYY\n")], None, cx); - }); - - let new_buffer_text = " - aaa - XXX - YYY - ddd - " - .unindent(); - let annotated_base = " - <0>aaa - <1<2<3bbb - ccc - 1>2>3>ddd - <4>" - .unindent(); - assert_rows_to_base_text_rows_visual( - &buffer, - &diff, - &new_buffer_text, - &annotated_base, - cx, - ); - } - - { - let base_text = " - aaa - bbb - ccc - ddd - " - .unindent(); - let buffer_text = " - aaa - ddd - " - .unindent(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(4..4, "XXX\nYYY\n")], None, cx); - }); - - let annotated_buffer = " - <0>aaa - <1<2<3XXX - YYY - 1>2>3>ddd - <4>" - .unindent(); - assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx); - } - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let buffer_text = " - aaa - XXXX - ccc - " - .unindent(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(2..10, "YY\nZZ")], None, cx); - }); - - let new_buffer_text = " - aaYY - ZZcc - " - .unindent(); - let annotated_base = " - <0>aa<1a - bbb - c1>cc - <2>" - .unindent(); - assert_rows_to_base_text_rows_visual( - &buffer, - &diff, - &new_buffer_text, - &annotated_base, - cx, - ); - } - - { - let base_text = " - aaa - bbb - ccc - " - .unindent(); - let buffer_text = " - aaa - XXXX - ccc - " - .unindent(); - let (buffer, diff) = make_diff(&base_text, &buffer_text, cx); - - buffer.update(cx, |buffer, cx| { - buffer.edit([(0..9, "ZZ\n")], None, cx); - }); - - let new_buffer_text = " - ZZ - ccc - " - .unindent(); - let annotated_base = " - <0<1aaa - bbb - 0>1>ccc - <2>" - .unindent(); - assert_rows_to_base_text_rows_visual( - &buffer, - &diff, - &new_buffer_text, - &annotated_base, - cx, - ); - } - } - - #[gpui::test] - async fn test_row_translation_no_base_text(cx: &mut gpui::TestAppContext) { - let buffer_text = "aaa\nbbb\nccc\n"; - let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx)); - let diff = cx.new(|cx| BufferDiff::new(&buffer.read(cx).text_snapshot(), cx)); - - let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot()); - let diff_snapshot = diff.update(cx, |diff, cx| diff.snapshot(cx)); - - let points = vec![ - Point::new(0, 0), - Point::new(1, 0), - Point::new(2, 0), - Point::new(3, 0), - ]; - let base_rows: Vec<_> = diff_snapshot - .points_to_base_text_points(points, &buffer_snapshot) - .0 - .collect(); - - let zero = Point::new(0, 0); - assert_eq!( - base_rows, - vec![zero..zero, zero..zero, zero..zero, zero..zero], - "all buffer rows should map to point 0,0 in empty base text" - ); - - let base_points = vec![Point::new(0, 0)]; - let (rows_iter, _, _) = - diff_snapshot.base_text_points_to_points(base_points, &buffer_snapshot); - let buffer_rows: Vec<_> = rows_iter.collect(); - - let max_point = buffer_snapshot.max_point(); - assert_eq!( - buffer_rows, - vec![zero..max_point], - "base text row 0 should map to entire buffer range" - ); - } } diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 696c1aed868047a110ec290332e298e9c801db45..0f83c0c518de998158b618b046f8ef49e84677cc 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -150,12 +150,11 @@ type TextHighlights = TreeMap>; #[derive(Debug)] -pub struct MultiBufferRowMapping { - pub first_group: Option>, - pub boundaries: Vec<(MultiBufferPoint, Range)>, - pub prev_boundary: Option<(MultiBufferPoint, Range)>, - pub source_excerpt_end: MultiBufferPoint, - pub target_excerpt_end: MultiBufferPoint, +pub struct CompanionExcerptPatch { + pub patch: Patch, + pub edited_range: Range, + pub source_excerpt_range: Range, + pub target_excerpt_range: Range, } pub type ConvertMultiBufferRows = fn( @@ -163,7 +162,7 @@ pub type ConvertMultiBufferRows = fn( &MultiBufferSnapshot, &MultiBufferSnapshot, (Bound, Bound), -) -> Vec; +) -> Vec; /// Decides how text in a [`MultiBuffer`] should be displayed in a buffer, handling inlay hints, /// folding, hard tabs, soft wrapping, custom blocks (like diagnostics), and highlighting. @@ -233,7 +232,7 @@ impl Companion { companion_snapshot: &MultiBufferSnapshot, our_snapshot: &MultiBufferSnapshot, bounds: (Bound, Bound), - ) -> Vec { + ) -> Vec { let (excerpt_map, convert_fn) = if display_map_id == self.rhs_display_map_id { (&self.rhs_excerpt_to_lhs_excerpt, self.rhs_rows_to_lhs_rows) } else { @@ -242,19 +241,32 @@ impl Companion { convert_fn(excerpt_map, companion_snapshot, our_snapshot, bounds) } - pub(crate) fn convert_rows_from_companion( + pub(crate) fn convert_point_from_companion( &self, display_map_id: EntityId, our_snapshot: &MultiBufferSnapshot, companion_snapshot: &MultiBufferSnapshot, - bounds: (Bound, Bound), - ) -> Vec { + point: MultiBufferPoint, + ) -> Range { let (excerpt_map, convert_fn) = if display_map_id == self.rhs_display_map_id { (&self.lhs_excerpt_to_rhs_excerpt, self.lhs_rows_to_rhs_rows) } else { (&self.rhs_excerpt_to_lhs_excerpt, self.rhs_rows_to_lhs_rows) }; - convert_fn(excerpt_map, our_snapshot, companion_snapshot, bounds) + + let excerpt = convert_fn( + excerpt_map, + our_snapshot, + companion_snapshot, + (Bound::Included(point), Bound::Included(point)), + ) + .into_iter() + .next(); + + let Some(excerpt) = excerpt else { + return Point::zero()..our_snapshot.max_point(); + }; + excerpt.patch.edit_for_old_position(point).new } pub(crate) fn companion_excerpt_to_excerpt( diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 4a4d6881e538a650d98cbc2b25385640b1617935..7c926cbfbb46845833688cbb854eaa1af468bb1d 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -656,36 +656,25 @@ impl BlockMap { .to_point(WrapPoint::new(edit.new.end, 0), Bias::Left); let my_start = companion - .convert_rows_from_companion( + .convert_point_from_companion( display_map_id, wrap_snapshot.buffer_snapshot(), companion_new_snapshot.buffer_snapshot(), - ( - Bound::Included(companion_start), - Bound::Included(companion_start), - ), + companion_start, ) - .first() - .and_then(|t| t.boundaries.first()) - .map(|(_, range)| range.start) - .unwrap_or(wrap_snapshot.buffer_snapshot().max_point()); + .start; let my_end = companion - .convert_rows_from_companion( + .convert_point_from_companion( display_map_id, wrap_snapshot.buffer_snapshot(), companion_new_snapshot.buffer_snapshot(), - ( - Bound::Included(companion_end), - Bound::Included(companion_end), - ), + companion_end, ) - .first() - .and_then(|t| t.boundaries.last()) - .map(|(_, range)| range.end) - .unwrap_or(wrap_snapshot.buffer_snapshot().max_point()); + .end; let mut my_start = wrap_snapshot.make_wrap_point(my_start, Bias::Left); let mut my_end = wrap_snapshot.make_wrap_point(my_end, Bias::Left); + // TODO(split-diff) should use trailing_excerpt_update_count for the second case if my_end.column() > 0 || my_end == wrap_snapshot.max_point() { *my_end.row_mut() += 1; *my_end.column_mut() = 0; @@ -1104,7 +1093,7 @@ impl BlockMap { let our_buffer = wrap_snapshot.buffer_snapshot(); let companion_buffer = companion_snapshot.buffer_snapshot(); - let row_mappings = companion.convert_rows_to_companion( + let patches = companion.convert_rows_to_companion( display_map_id, companion_buffer, our_buffer, @@ -1129,14 +1118,27 @@ impl BlockMap { let mut result = Vec::new(); - for row_mapping in row_mappings { - let mut iter = row_mapping.boundaries.iter().cloned().peekable(); + // old approach: buffer_diff computed the patch, and then passed a chosen sequence of points through it, returning the results + // new approach: buffer_diff gives us the patch directly, and then we pass through the points we are interested in + for excerpt in patches { + let mut source_points = (excerpt.edited_range.start.row..=excerpt.edited_range.end.row) + .map(|row| MultiBufferPoint::new(row, 0)) + .chain(if excerpt.edited_range.end.column > 0 { + Some(excerpt.edited_range.end) + } else { + None + }) + .peekable(); + let last_source_point = if excerpt.edited_range.end.column > 0 { + excerpt.edited_range.end + } else { + MultiBufferPoint::new(excerpt.edited_range.end.row, 0) + }; - let Some(((first_boundary, first_range), first_group)) = - iter.peek().cloned().zip(row_mapping.first_group.clone()) - else { + let Some(first_point) = source_points.peek().copied() else { continue; }; + let edit_for_first_point = excerpt.patch.edit_for_old_position(first_point); // Because we calculate spacers based on differences in wrap row // counts between the RHS and LHS for corresponding buffer points, @@ -1145,12 +1147,20 @@ impl BlockMap { // counts should have been balanced already by spacers above this // edit, so we only need to insert spacers for when the difference // in counts diverges from that baseline value. - let (our_baseline, their_baseline) = if first_group.start < first_boundary { - (first_group.start, first_range.start) - } else if let Some((prev_boundary, prev_range)) = row_mapping.prev_boundary { - (prev_boundary, prev_range.end) + let (our_baseline, their_baseline) = if edit_for_first_point.old.start < first_point { + // Case 1: We are inside a hunk/group--take the start of the hunk/group on both sides as the baseline. + ( + edit_for_first_point.old.start, + edit_for_first_point.new.start, + ) + } else if first_point.row > excerpt.source_excerpt_range.start.row { + // Case 2: We are not inside a hunk/group--go back by one row to find the baseline. + let prev_point = Point::new(first_point.row - 1, 0); + let edit_for_prev_point = excerpt.patch.edit_for_old_position(prev_point); + (prev_point, edit_for_prev_point.new.end) } else { - (first_boundary, first_range.start) + // Case 3: We are at the start of the excerpt--no previous row to use as the baseline. + (first_point, edit_for_first_point.new.start) }; let our_baseline = wrap_snapshot .make_wrap_point(our_baseline, Bias::Left) @@ -1161,14 +1171,17 @@ impl BlockMap { let mut delta = their_baseline.0 as i32 - our_baseline.0 as i32; - if first_group.start < first_boundary { - let mut current_boundary = first_boundary; - let current_range = first_range; - while let Some((next_boundary, next_range)) = iter.peek().cloned() - && next_range.end <= current_range.end - { - iter.next(); - current_boundary = next_boundary; + // If we started out in the middle of a hunk/group, work up to the end of that group to set up the main loop below. + if edit_for_first_point.old.start < first_point { + let mut current_boundary = first_point; + let current_range = edit_for_first_point.new; + while let Some(next_point) = source_points.peek().cloned() { + let edit_for_next_point = excerpt.patch.edit_for_old_position(next_point); + if edit_for_next_point.new.end > current_range.end { + break; + } + source_points.next(); + current_boundary = next_point; } let (new_delta, spacer) = @@ -1187,13 +1200,14 @@ impl BlockMap { } } - while let Some((boundary, range)) = iter.next() { - let mut current_boundary = boundary; - let current_range = range; + // Main loop: process one hunk/group at a time, possibly inserting spacers before and after. + while let Some(source_point) = source_points.next() { + let mut current_boundary = source_point; + let current_range = excerpt.patch.edit_for_old_position(current_boundary).new; // This can only occur at the end of an excerpt. if current_boundary.column > 0 { - debug_assert_eq!(current_boundary, row_mapping.source_excerpt_end); + debug_assert_eq!(current_boundary, excerpt.source_excerpt_range.end); break; } @@ -1202,9 +1216,12 @@ impl BlockMap { determine_spacer(current_boundary, current_range.start, delta); delta = delta_at_start; - while let Some((next_boundary, next_range)) = iter.peek() - && next_range.end <= current_range.end - { + while let Some(next_point) = source_points.peek().copied() { + let edit_for_next_point = excerpt.patch.edit_for_old_position(next_point); + if edit_for_next_point.new.end > current_range.end { + break; + } + if let Some((wrap_row, height)) = spacer_at_start.take() { result.push(( BlockPlacement::Above(wrap_row), @@ -1216,13 +1233,13 @@ impl BlockMap { )); } - current_boundary = *next_boundary; - iter.next(); + current_boundary = next_point; + source_points.next(); } // This can only occur at the end of an excerpt. if current_boundary.column > 0 { - debug_assert_eq!(current_boundary, row_mapping.source_excerpt_end); + debug_assert_eq!(current_boundary, excerpt.source_excerpt_range.end); break; } @@ -1254,10 +1271,9 @@ impl BlockMap { } } - let (last_boundary, _last_range) = row_mapping.boundaries.last().cloned().unwrap(); - if last_boundary == row_mapping.source_excerpt_end { + if last_source_point == excerpt.source_excerpt_range.end { let (_new_delta, spacer) = - determine_spacer(last_boundary, row_mapping.target_excerpt_end, delta); + determine_spacer(last_source_point, excerpt.target_excerpt_range.end, delta); if let Some((wrap_row, height)) = spacer { result.push(( BlockPlacement::Below(wrap_row), diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 7d937866b89b4b3ed3b615ae661f566777b146a5..b0dae69e82bdfae79d58817e165aad80002044b0 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -22742,29 +22742,24 @@ impl Editor { buffer_ranges.last() }?; - let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row; - let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row; + let buffer_range = range.to_point(buffer); let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else { - let selection = start_row_in_buffer..end_row_in_buffer; - - return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection)); + return Some(( + multi_buffer.buffer(buffer.remote_id()).unwrap(), + buffer_range.start.row..buffer_range.end.row, + )); }; let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx); - let (mut translated, _, _) = buffer_diff_snapshot.points_to_base_text_points( - [ - Point::new(start_row_in_buffer, 0), - Point::new(end_row_in_buffer, 0), - ], - buffer, - ); - let start_row = translated.next().unwrap().start.row; - let end_row = translated.next().unwrap().end.row; + let start = + buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer); + let end = + buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer); Some(( multi_buffer.buffer(buffer.remote_id()).unwrap(), - start_row..end_row, + start.row..end.row, )) }); diff --git a/crates/editor/src/split.rs b/crates/editor/src/split.rs index 2268bc5a8062f5f9ad15f92c9f0a3f7bdea32056..f6f720f348f60bcc7da43a2fd33defe63acbaf41 100644 --- a/crates/editor/src/split.rs +++ b/crates/editor/src/split.rs @@ -1,4 +1,4 @@ -use std::ops::{Bound, Range}; +use std::ops::{Bound, Range, RangeInclusive}; use buffer_diff::{BufferDiff, BufferDiffSnapshot}; use collections::HashMap; @@ -6,19 +6,19 @@ use feature_flags::{FeatureFlag, FeatureFlagAppExt as _}; use gpui::{Action, AppContext as _, Entity, EventEmitter, Focusable, Subscription, WeakEntity}; use language::{Buffer, Capability}; use multi_buffer::{ - Anchor, BufferOffset, ExcerptId, ExcerptRange, ExpandExcerptDirection, MultiBuffer, - MultiBufferPoint, MultiBufferSnapshot, PathKey, + Anchor, ExcerptId, ExcerptRange, ExpandExcerptDirection, MultiBuffer, MultiBufferPoint, + MultiBufferSnapshot, PathKey, }; use project::Project; use rope::Point; -use text::{OffsetRangeExt as _, ToPoint as _}; +use text::{OffsetRangeExt as _, Patch, ToPoint as _}; use ui::{ App, Context, InteractiveElement as _, IntoElement as _, ParentElement as _, Render, Styled as _, Window, div, }; use crate::{ - display_map::MultiBufferRowMapping, + display_map::CompanionExcerptPatch, split_editor_view::{SplitEditorState, SplitEditorView}, }; use workspace::{ActivatePaneLeft, ActivatePaneRight, Item, Workspace}; @@ -35,17 +35,13 @@ pub(crate) fn convert_lhs_rows_to_rhs( rhs_snapshot: &MultiBufferSnapshot, lhs_snapshot: &MultiBufferSnapshot, lhs_bounds: (Bound, Bound), -) -> Vec { - convert_rows( +) -> Vec { + patches_for_range( lhs_excerpt_to_rhs_excerpt, lhs_snapshot, rhs_snapshot, lhs_bounds, - |diff, points, buffer| { - let (points, first_group, prev_boundary) = - diff.base_text_points_to_points(points, buffer); - (points.collect(), first_group, prev_boundary) - }, + |diff, range, buffer| diff.patch_for_base_text_range(range, buffer), ) } @@ -54,182 +50,171 @@ pub(crate) fn convert_rhs_rows_to_lhs( lhs_snapshot: &MultiBufferSnapshot, rhs_snapshot: &MultiBufferSnapshot, rhs_bounds: (Bound, Bound), -) -> Vec { - convert_rows( +) -> Vec { + patches_for_range( rhs_excerpt_to_lhs_excerpt, rhs_snapshot, lhs_snapshot, rhs_bounds, - |diff, points, buffer| { - let (points, first_group, prev_boundary) = - diff.points_to_base_text_points(points, buffer); - (points.collect(), first_group, prev_boundary) - }, + |diff, range, buffer| diff.patch_for_buffer_range(range, buffer), ) } -fn convert_rows( +fn patches_for_range( excerpt_map: &HashMap, source_snapshot: &MultiBufferSnapshot, target_snapshot: &MultiBufferSnapshot, source_bounds: (Bound, Bound), translate_fn: F, -) -> Vec +) -> Vec where - F: Fn( - &BufferDiffSnapshot, - Vec, - &text::BufferSnapshot, - ) -> ( - Vec>, - Option>, - Option<(Point, Range)>, - ), + F: Fn(&BufferDiffSnapshot, RangeInclusive, &text::BufferSnapshot) -> Patch, { let mut result = Vec::new(); + let mut patches = HashMap::default(); - for (buffer, buffer_offset_range, source_excerpt_id) in + for (source_buffer, buffer_offset_range, source_excerpt_id) in source_snapshot.range_to_buffer_ranges(source_bounds) { - if let Some(translation) = convert_excerpt_rows( - excerpt_map, + let target_excerpt_id = excerpt_map.get(&source_excerpt_id).copied().unwrap(); + let target_buffer = target_snapshot + .buffer_for_excerpt(target_excerpt_id) + .unwrap(); + let patch = patches.entry(source_buffer.remote_id()).or_insert_with(|| { + let diff = source_snapshot + .diff_for_buffer_id(source_buffer.remote_id()) + .unwrap(); + let rhs_buffer = if source_buffer.remote_id() == diff.base_text().remote_id() { + &target_buffer + } else { + source_buffer + }; + // TODO(split-diff) pass only the union of the ranges for the affected excerpts + translate_fn(diff, Point::zero()..=source_buffer.max_point(), rhs_buffer) + }); + let buffer_point_range = buffer_offset_range.to_point(source_buffer); + + // TODO(split-diff) maybe narrow the patch to only the edited part of the excerpt + // (less useful for project diff, but important if we want to do singleton side-by-side diff) + result.push(patch_for_excerpt( source_snapshot, target_snapshot, source_excerpt_id, - buffer, - buffer_offset_range, - &translate_fn, - ) { - result.push(translation); - } + target_excerpt_id, + source_buffer, + target_buffer, + patch, + buffer_point_range, + )); } result } -fn convert_excerpt_rows( - excerpt_map: &HashMap, +fn patch_for_excerpt( source_snapshot: &MultiBufferSnapshot, target_snapshot: &MultiBufferSnapshot, source_excerpt_id: ExcerptId, + target_excerpt_id: ExcerptId, source_buffer: &text::BufferSnapshot, - source_buffer_range: Range, - translate_fn: F, -) -> Option -where - F: Fn( - &BufferDiffSnapshot, - Vec, - &text::BufferSnapshot, - ) -> ( - Vec>, - Option>, - Option<(Point, Range)>, - ), -{ - let target_excerpt_id = excerpt_map.get(&source_excerpt_id).copied()?; - let target_buffer = target_snapshot.buffer_for_excerpt(target_excerpt_id)?; - - let diff = source_snapshot.diff_for_buffer_id(source_buffer.remote_id())?; - let rhs_buffer = if source_buffer.remote_id() == diff.base_text().remote_id() { - &target_buffer - } else { - source_buffer - }; - - let local_start = source_buffer.offset_to_point(source_buffer_range.start.0); - let local_end = source_buffer.offset_to_point(source_buffer_range.end.0); - - let mut input_points: Vec = (local_start.row..=local_end.row) - .map(|row| Point::new(row, 0)) - .collect(); - if local_end.column > 0 { - input_points.push(local_end); - } - - let (translated_ranges, first_group, prev_boundary) = - translate_fn(&diff, input_points.clone(), rhs_buffer); - - let source_multibuffer_range = source_snapshot.range_for_excerpt(source_excerpt_id)?; + target_buffer: &text::BufferSnapshot, + patch: &Patch, + source_edited_range: Range, +) -> CompanionExcerptPatch { + let source_multibuffer_range = source_snapshot + .range_for_excerpt(source_excerpt_id) + .unwrap(); let source_excerpt_start_in_multibuffer = source_multibuffer_range.start; - let source_context_range = source_snapshot.context_range_for_excerpt(source_excerpt_id)?; + let source_context_range = source_snapshot + .context_range_for_excerpt(source_excerpt_id) + .unwrap(); let source_excerpt_start_in_buffer = source_context_range.start.to_point(&source_buffer); let source_excerpt_end_in_buffer = source_context_range.end.to_point(&source_buffer); - let target_multibuffer_range = target_snapshot.range_for_excerpt(target_excerpt_id)?; + let target_multibuffer_range = target_snapshot + .range_for_excerpt(target_excerpt_id) + .unwrap(); let target_excerpt_start_in_multibuffer = target_multibuffer_range.start; - let target_context_range = target_snapshot.context_range_for_excerpt(target_excerpt_id)?; + let target_context_range = target_snapshot + .context_range_for_excerpt(target_excerpt_id) + .unwrap(); let target_excerpt_start_in_buffer = target_context_range.start.to_point(&target_buffer); let target_excerpt_end_in_buffer = target_context_range.end.to_point(&target_buffer); - let boundaries: Vec<_> = input_points - .into_iter() - .zip(translated_ranges) - .map(|(source_buffer_point, target_range)| { - let source_multibuffer_point = source_excerpt_start_in_multibuffer - + (source_buffer_point - source_excerpt_start_in_buffer.min(source_buffer_point)); - - let clamped_target_start = target_range + let edits = patch + .edits() + .iter() + .skip_while(|edit| edit.old.end < source_excerpt_start_in_buffer) + .take_while(|edit| edit.old.start <= source_excerpt_end_in_buffer) + .map(|edit| { + let clamped_source_start = edit + .old + .start + .max(source_excerpt_start_in_buffer) + .min(source_excerpt_end_in_buffer); + let clamped_source_end = edit + .old + .end + .max(source_excerpt_start_in_buffer) + .min(source_excerpt_end_in_buffer); + let source_multibuffer_start = source_excerpt_start_in_multibuffer + + (clamped_source_start - source_excerpt_start_in_buffer); + let source_multibuffer_end = source_excerpt_start_in_multibuffer + + (clamped_source_end - source_excerpt_start_in_buffer); + let clamped_target_start = edit + .new .start .max(target_excerpt_start_in_buffer) .min(target_excerpt_end_in_buffer); - let clamped_target_end = target_range + let clamped_target_end = edit + .new .end .max(target_excerpt_start_in_buffer) .min(target_excerpt_end_in_buffer); - let target_multibuffer_start = target_excerpt_start_in_multibuffer + (clamped_target_start - target_excerpt_start_in_buffer); - let target_multibuffer_end = target_excerpt_start_in_multibuffer + (clamped_target_end - target_excerpt_start_in_buffer); + text::Edit { + old: source_multibuffer_start..source_multibuffer_end, + new: target_multibuffer_start..target_multibuffer_end, + } + }); + + let edits = [text::Edit { + old: source_excerpt_start_in_multibuffer..source_excerpt_start_in_multibuffer, + new: target_excerpt_start_in_multibuffer..target_excerpt_start_in_multibuffer, + }] + .into_iter() + .chain(edits); - ( - source_multibuffer_point, - target_multibuffer_start..target_multibuffer_end, - ) - }) - .collect(); - let first_group = first_group.map(|first_group| { - let start = source_excerpt_start_in_multibuffer - + (first_group.start - source_excerpt_start_in_buffer.min(first_group.start)); - let end = source_excerpt_start_in_multibuffer - + (first_group.end - source_excerpt_start_in_buffer.min(first_group.end)); - start..end - }); - - let prev_boundary = prev_boundary.map(|(source_buffer_point, target_range)| { - let source_multibuffer_point = source_excerpt_start_in_multibuffer - + (source_buffer_point - source_excerpt_start_in_buffer.min(source_buffer_point)); - - let clamped_target_start = target_range - .start - .max(target_excerpt_start_in_buffer) - .min(target_excerpt_end_in_buffer); - let clamped_target_end = target_range - .end - .max(target_excerpt_start_in_buffer) - .min(target_excerpt_end_in_buffer); - - let target_multibuffer_start = target_excerpt_start_in_multibuffer - + (clamped_target_start - target_excerpt_start_in_buffer); - let target_multibuffer_end = target_excerpt_start_in_multibuffer - + (clamped_target_end - target_excerpt_start_in_buffer); - - ( - source_multibuffer_point, - target_multibuffer_start..target_multibuffer_end, - ) - }); - - Some(MultiBufferRowMapping { - boundaries, - first_group, - prev_boundary, - source_excerpt_end: source_excerpt_start_in_multibuffer - + (source_excerpt_end_in_buffer - source_excerpt_start_in_buffer), - target_excerpt_end: target_excerpt_start_in_multibuffer - + (target_excerpt_end_in_buffer - target_excerpt_start_in_buffer), - }) + let mut merged_edits: Vec> = Vec::new(); + for edit in edits { + if let Some(last) = merged_edits.last_mut() { + if edit.new.start <= last.new.end { + last.old.end = last.old.end.max(edit.old.end); + last.new.end = last.new.end.max(edit.new.end); + continue; + } + } + merged_edits.push(edit); + } + + let edited_range = source_excerpt_start_in_multibuffer + + (source_edited_range.start - source_excerpt_start_in_buffer) + ..source_excerpt_start_in_multibuffer + + (source_edited_range.end - source_excerpt_start_in_buffer); + + let source_excerpt_end = source_excerpt_start_in_multibuffer + + (source_excerpt_end_in_buffer - source_excerpt_start_in_buffer); + let target_excerpt_end = target_excerpt_start_in_multibuffer + + (target_excerpt_end_in_buffer - target_excerpt_start_in_buffer); + + CompanionExcerptPatch { + patch: Patch::new(merged_edits), + edited_range, + source_excerpt_range: source_excerpt_start_in_multibuffer..source_excerpt_end, + target_excerpt_range: target_excerpt_start_in_multibuffer..target_excerpt_end, + } } pub struct SplitDiffFeatureFlag; @@ -623,24 +608,16 @@ impl SplittableEditor { let source_snapshot = source_multibuffer.read(cx).snapshot(cx); let target_snapshot = target_multibuffer.read(cx).snapshot(cx); - let target_point = target_editor.update(cx, |target_editor, cx| { + let target_range = target_editor.update(cx, |target_editor, cx| { target_editor.display_map.update(cx, |display_map, cx| { let display_map_id = cx.entity_id(); display_map.companion().unwrap().update(cx, |companion, _| { - companion - .convert_rows_from_companion( - display_map_id, - &target_snapshot, - &source_snapshot, - (Bound::Included(source_point), Bound::Included(source_point)), - ) - .first() - .unwrap() - .boundaries - .first() - .unwrap() - .1 - .start + companion.convert_point_from_companion( + display_map_id, + &target_snapshot, + &source_snapshot, + source_point, + ) }) }) }); @@ -648,7 +625,7 @@ impl SplittableEditor { target_editor.update(cx, |editor, cx| { editor.set_suppress_selection_callback(true); editor.change_selections(crate::SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([target_point..target_point]); + s.select_ranges([target_range]); }); editor.set_suppress_selection_callback(false); }); @@ -1501,14 +1478,17 @@ impl LhsEditor { .into_iter() .map(|(_, excerpt_range)| { let point_range_to_base_text_point_range = |range: Range| { - let (mut translated, _, _) = diff_snapshot.points_to_base_text_points( - [Point::new(range.start.row, 0), Point::new(range.end.row, 0)], - main_buffer, - ); - let start_row = translated.next().unwrap().start.row; - let end_row = translated.next().unwrap().end.row; - let end_column = diff_snapshot.base_text().line_len(end_row); - Point::new(start_row, 0)..Point::new(end_row, end_column) + let start = diff_snapshot + .buffer_point_to_base_text_range( + Point::new(range.start.row, 0), + main_buffer, + ) + .start; + let end = diff_snapshot + .buffer_point_to_base_text_range(Point::new(range.end.row, 0), main_buffer) + .end; + let end_column = diff_snapshot.base_text().line_len(end.row); + Point::new(start.row, 0)..Point::new(end.row, end_column) }; let rhs = excerpt_range.primary.to_point(main_buffer); let context = excerpt_range.context.to_point(main_buffer); diff --git a/crates/text/src/patch.rs b/crates/text/src/patch.rs index ec495f60fd78bdd3618a8b44ffb59e833439f629..c9f353a9cce22b54ecf7e2fa872035cd486438e6 100644 --- a/crates/text/src/patch.rs +++ b/crates/text/src/patch.rs @@ -225,6 +225,46 @@ where old } } + + /// Returns the edit that touches the given old position. + /// + /// An edit is considered to touch the given old position if edit.old.start <= old <= edit.old.end (note, inclusive on the right). + /// + /// If there are no edits touching the given old position, an empty edit with appropriate (empty) old and new ranges is returned. + pub fn edit_for_old_position(&self, old: T) -> Edit { + let edits = self.edits(); + + let ix = match edits.binary_search_by(|probe| probe.old.start.cmp(&old)) { + Ok(ix) => ix, + Err(ix) => { + if ix == 0 { + return Edit { + old: old..old, + new: old..old, + }; + } else { + ix - 1 + } + } + }; + + if let Some(edit) = edits.get(ix) { + if old > edit.old.end { + let translated = edit.new.end + (old - edit.old.end); + Edit { + new: translated..translated, + old: old..old, + } + } else { + edit.clone() + } + } else { + Edit { + old: old..old, + new: old..old, + } + } + } } impl Patch {