use crate::{ excerpt_map::{ ExcerptMap, ExcerptMapChunks, ExcerptMapRows, ExcerptMapSnapshot, ExcerptOffset, ExcerptPoint, ExcerptRow, }, position::{TypedOffset, TypedPoint, TypedPointUtf16, TypedRow}, RowInfo, }; use super::{ Anchor, AnchorRangeExt, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, ToOffset, ToPoint as _, }; use collections::HashMap; use git::diff::DiffHunkStatus; use gpui::{AppContext, Context as _, HighlightStyle, Model, ModelContext, Subscription}; use language::{BufferChunks, BufferId, Chunk}; use project::buffer_store::BufferChangeSet; use std::{ any::TypeId, mem::{self}, ops::Range, sync::Arc, }; use sum_tree::{Cursor, SumTree, TreeMap}; use text::{Bias, Edit, Patch, Point, TextSummary, ToOffset as _, ToPoint as _}; pub(crate) struct DiffMap { excerpt_map: ExcerptMap, snapshot: DiffMapSnapshot, diff_bases: HashMap, all_hunks_expanded: bool, edits_since_sync: Patch, } struct ChangeSetState { change_set: Model, _subscription: Subscription, } #[derive(Clone)] struct DiffSnapshot { diff: git::diff::BufferDiff, base_text: language::BufferSnapshot, base_text_version: usize, } #[derive(Clone)] pub struct DiffMapSnapshot { diffs: TreeMap, excerpt_map_snapshot: ExcerptMapSnapshot, pub(crate) transforms: SumTree, pub(crate) version: usize, } #[derive(Debug, Clone)] pub(crate) enum DiffTransform { BufferContent { summary: TextSummary, is_inserted_hunk: bool, }, DeletedHunk { summary_including_newline: TextSummary, buffer_id: BufferId, base_text_byte_range: Range, base_text_start: Point, needs_newline: bool, }, } #[derive(Debug, Clone)] pub(crate) struct DiffTransformSummary { excerpt_map: TextSummary, diff_map: TextSummary, } impl DiffTransformSummary { pub fn excerpt_point(&self) -> ExcerptPoint { ExcerptPoint::wrap(self.excerpt_map.lines) } pub fn multibuffer_offset(&self) -> ExcerptOffset { ExcerptOffset::new(self.excerpt_map.len) } pub fn diff_point(&self) -> DiffPoint { DiffPoint::wrap(self.diff_map.lines) } pub fn diff_offset(&self) -> DiffOffset { DiffOffset::new(self.diff_map.len) } } pub struct DiffMapChunks<'a> { snapshot: &'a DiffMapSnapshot, language_aware: bool, cursor: Cursor<'a, DiffTransform, (DiffOffset, ExcerptOffset)>, excerpt_map_chunks: ExcerptMapChunks<'a>, current_chunk: Option>, excerpt_map_offset: ExcerptOffset, offset: DiffOffset, end_offset: DiffOffset, diff_base_chunks: Option<(BufferId, BufferChunks<'a>)>, } #[derive(Clone)] pub struct DiffMapRows<'a> { cursor: Cursor<'a, DiffTransform, (DiffPoint, ExcerptPoint)>, diff_point: DiffPoint, input_buffer_rows: ExcerptMapRows<'a>, } pub type DiffEdit = text::Edit; #[derive(Debug)] enum ChangeKind { DiffUpdated { base_changed: bool, }, InputEdited, ExpandOrCollapseHunks { range: Range, expand: bool, }, } pub type DiffOffset = TypedOffset; pub type DiffPoint = TypedPoint; pub type DiffPointUtf16 = TypedPointUtf16; pub type DiffRow = TypedRow; impl DiffMap { pub fn new(excerpt_map: ExcerptMap, cx: &mut AppContext) -> Self { let excerpt_map_snapshot = excerpt_map.snapshot(cx); let snapshot = DiffMapSnapshot { diffs: TreeMap::default(), version: 0, transforms: SumTree::from_item( DiffTransform::BufferContent { summary: excerpt_map_snapshot.text_summary(), is_inserted_hunk: false, }, &(), ), excerpt_map_snapshot, }; Self { excerpt_map, snapshot, all_hunks_expanded: false, diff_bases: HashMap::default(), edits_since_sync: Patch::default(), } } pub fn add_change_set( &mut self, change_set: Model, cx: &mut ModelContext, ) { let buffer_id = change_set.read(cx).buffer_id; self.buffer_diff_changed(change_set.clone(), cx); self.diff_bases.insert( buffer_id, ChangeSetState { _subscription: cx.observe(&change_set, |multibuffer, change_set, cx| { multibuffer .diff_map .buffer_diff_changed(change_set.clone(), cx); }), change_set, }, ); } pub fn diff_base_for(&self, buffer_id: BufferId) -> Option<&Model> { self.diff_bases .get(&buffer_id) .map(|state| &state.change_set) } pub fn sync( &mut self, input_edits: Vec>, cx: &AppContext, ) -> (DiffMapSnapshot, Vec) { let changes = input_edits .iter() .map(|edit| (edit.clone(), ChangeKind::InputEdited)) .collect::>(); self.recompute_transforms(changes, cx); // we probably need to update our excerpt map snapshot at this point. todo!(); ( self.snapshot.clone(), mem::take(&mut self.edits_since_sync).into_inner(), ) } fn buffer_diff_changed(&mut self, change_set: Model, cx: &AppContext) { let change_set = change_set.read(cx); let buffer_id = change_set.buffer_id; let diff = change_set.diff_to_buffer.clone(); let base_text = change_set .base_text .as_ref() .map(|buffer| buffer.read(cx).snapshot()); let base_text_version_changed = self .snapshot .diffs .get(&buffer_id) .map_or(true, |snapshot| { snapshot.base_text_version != change_set.base_text_version }); if let Some(base_text) = base_text.clone() { self.snapshot.diffs.insert( buffer_id, DiffSnapshot { diff: diff.clone(), base_text, base_text_version: change_set.base_text_version, }, ); } else { self.snapshot.diffs.remove(&buffer_id); } let multibuffer_snapshot = &self.snapshot.excerpt_map_snapshot; let changes = (&self.excerpt_map) .ranges_for_buffer(buffer_id, cx) .into_iter() .map(|(_, range, _)| { let multibuffer_start = multibuffer_snapshot.point_to_offset(range.start); let multibuffer_end = multibuffer_snapshot.point_to_offset(range.end); ( text::Edit { old: multibuffer_start..multibuffer_end, new: multibuffer_start..multibuffer_end, }, ChangeKind::DiffUpdated { base_changed: base_text_version_changed, }, ) }) .collect(); self.recompute_transforms(changes, cx); } pub(super) fn has_multiple_hunks(&self) -> bool { self.snapshot .diff_hunks_in_range(DiffOffset::new(0)..self.snapshot.len()) .nth(1) .is_some() } pub(super) fn has_expanded_diff_hunks_in_ranges(&self, ranges: &[Range]) -> bool { let mut cursor = self.snapshot.transforms.cursor::(&()); let excerpt_map_snapshot = &self.snapshot.excerpt_map_snapshot; for range in ranges { let range = excerpt_map_snapshot .offset_to_point(self.snapshot.to_excerpt_offset(range.start)) ..excerpt_map_snapshot.offset_to_point(self.snapshot.to_excerpt_offset(range.end)); let mut start = excerpt_map_snapshot.point_to_offset(ExcerptPoint::new(range.start.row(), 0)); let mut end = excerpt_map_snapshot.point_to_offset(ExcerptPoint::new(range.end.row() + 1, 0)); start.value = start.value.saturating_sub(1); end = excerpt_map_snapshot.len().min(end + ExcerptOffset::new(1)); cursor.seek(&start, Bias::Right, &()); while *cursor.start() < end { match cursor.item() { Some(DiffTransform::DeletedHunk { .. }) | Some(DiffTransform::BufferContent { is_inserted_hunk: true, .. }) => return true, _ => {} } cursor.next(&()); } } false } pub(super) fn expand_diff_hunks(&mut self, ranges: Vec>, cx: &mut AppContext) { todo!(); // self.expand_or_collapse_diff_hunks(ranges, true, cx); } pub(super) fn collapse_diff_hunks(&mut self, ranges: Vec>, cx: &mut AppContext) { todo!(); // self.expand_or_collapse_diff_hunks(ranges, false, cx); } pub(super) fn set_all_hunks_expanded(&mut self, cx: &mut AppContext) { todo!(); // self.all_hunks_expanded = true; // self.expand_or_collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], true, cx); } fn expand_or_collapse_diff_hunks( &mut self, ranges: Vec>, expand: bool, cx: &AppContext, ) { let excerpt_map_snapshot = &self.snapshot.excerpt_map_snapshot; let mut changes = Vec::new(); for range in ranges.iter() { let excerpt_start = self .snapshot .excerpt_map_snapshot .offset_to_point(self.snapshot.to_excerpt_offset(range.start)); let excerpt_end = self .snapshot .excerpt_map_snapshot .offset_to_point(self.snapshot.to_excerpt_offset(range.start)); let excerpt_start = excerpt_map_snapshot.point_to_offset(ExcerptPoint::new(excerpt_start.row(), 0)); let excerpt_end = excerpt_map_snapshot.point_to_offset(ExcerptPoint::new(excerpt_end.row() + 1, 0)); let expanded_start = excerpt_start.saturating_sub(ExcerptOffset::new(1)); let expanded_end = excerpt_map_snapshot.len().min(excerpt_end); changes.push(( text::Edit { old: expanded_start..expanded_end, new: expanded_start..expanded_end, }, ChangeKind::ExpandOrCollapseHunks { range: excerpt_start..excerpt_end, expand, }, )); } self.recompute_transforms(changes, cx); } fn recompute_transforms( &mut self, changes: Vec<(text::Edit, ChangeKind)>, cx: &AppContext, ) { let mut cursor = self .snapshot .transforms .cursor::<(ExcerptOffset, DiffOffset)>(&()); let mut new_transforms = SumTree::default(); let mut edits = Patch::default(); let mut changes = changes.into_iter().peekable(); let mut delta = 0_isize; while let Some((mut edit, mut operation)) = changes.next() { let mut to_skip = cursor.slice(&edit.old.start, Bias::Left, &()); while cursor.end(&()).0 < edit.old.start || (cursor.end(&()).0 == edit.old.start && cursor.start().0 < edit.old.start) { to_skip.extend(cursor.item().cloned(), &()); cursor.next(&()); } self.append_transforms(&mut new_transforms, to_skip); let mut end_of_current_insert = ExcerptOffset::new(0); loop { let excerpt_map_range = edit.new.clone(); let edit_old_start = cursor.start().1 + DiffOffset::new((edit.old.start - cursor.start().0).value); let mut edit_old_end = cursor.start().1 + DiffOffset::new((edit.old.end - cursor.start().0).value); for (buffer, buffer_range, excerpt_id) in self .snapshot .excerpt_map_snapshot .range_to_buffer_ranges(excerpt_map_range.clone()) { let excerpt_range = self .snapshot .excerpt_map_snapshot .range_for_excerpt::(excerpt_id) .unwrap(); let excerpt_buffer_range = self .snapshot .excerpt_map_snapshot .buffer_range_for_excerpt(excerpt_id) .unwrap(); let buffer_id = buffer.remote_id(); let diff_state = self.snapshot.diffs.get(&buffer_id); let buffer_anchor_range = buffer.anchor_before(buffer_range.start) ..buffer.anchor_after(buffer_range.end); let change_start_buffer_offset = buffer_range.start; if let Some(diff_state) = diff_state { let diff = &diff_state.diff; let base_text = &diff_state.base_text; for hunk in diff.hunks_intersecting_range(buffer_anchor_range, &buffer) { let hunk_start_buffer_offset = hunk.buffer_range.start.to_offset(&buffer); let hunk_end_buffer_offset = hunk.buffer_range.end.to_offset(&buffer); let excerpt_buffer_range_start_offset = excerpt_buffer_range.start.to_offset(&buffer); let hunk_start_multibuffer_offset = excerpt_range.start + ExcerptOffset::new( hunk_start_buffer_offset .saturating_sub(excerpt_buffer_range_start_offset), ); let hunk_end_multibuffer_offset = excerpt_range.start + ExcerptOffset::new( hunk_end_buffer_offset .saturating_sub(excerpt_buffer_range_start_offset), ); self.push_buffer_content_transform( &mut new_transforms, hunk_start_multibuffer_offset, end_of_current_insert, ); while cursor.end(&()).0 < hunk_start_multibuffer_offset || (cursor.end(&()).0 == hunk_start_multibuffer_offset && cursor.start().0 < hunk_start_multibuffer_offset) { let Some(item) = cursor.item() else { break; }; if let DiffTransform::DeletedHunk { .. } = item { let old_range = cursor.start().1..cursor.end(&()).1; let new_offset = DiffOffset::new( (old_range.start.value as isize + delta) as usize, ); delta -= (old_range.end - old_range.start).value as isize; let edit = Edit { old: old_range, new: new_offset..new_offset, }; edits.push(edit); } cursor.next(&()); } let mut was_previously_expanded = false; let mut previous_expanded_summary = TextSummary::default(); if cursor.start().0 == hunk_start_multibuffer_offset { match cursor.item() { Some(DiffTransform::DeletedHunk { summary_including_newline, .. }) => { was_previously_expanded = true; previous_expanded_summary = summary_including_newline.clone(); } Some(DiffTransform::BufferContent { is_inserted_hunk, .. }) => { was_previously_expanded = *is_inserted_hunk; } None => {} }; } let hunk_is_deletion = hunk_start_buffer_offset == hunk_end_buffer_offset; let should_expand_hunk = match &operation { ChangeKind::DiffUpdated { base_changed: true } => { self.all_hunks_expanded } ChangeKind::ExpandOrCollapseHunks { range, expand } => { let intersects = hunk_is_deletion || (hunk_start_multibuffer_offset < range.end && hunk_end_multibuffer_offset > range.start); if *expand { was_previously_expanded || self.all_hunks_expanded || intersects } else { !intersects && (was_previously_expanded || self.all_hunks_expanded) } } _ => was_previously_expanded || self.all_hunks_expanded, }; if should_expand_hunk { if hunk.diff_base_byte_range.len() > 0 && hunk_start_buffer_offset >= change_start_buffer_offset { let mut text_cursor = base_text.as_rope().cursor(0); let base_text_start = text_cursor .summary::(hunk.diff_base_byte_range.start); let mut base_text_summary = text_cursor .summary::(hunk.diff_base_byte_range.end); let mut needs_newline = false; let mut diff_len = DiffOffset::new(hunk.diff_base_byte_range.len()); if base_text_summary.last_line_chars > 0 { diff_len.value += 1; base_text_summary.add_newline(); needs_newline = true; } if !was_previously_expanded || base_text_summary != previous_expanded_summary { let hunk_overshoot = hunk_start_multibuffer_offset - cursor.start().0; let old_start = cursor.start().1 + DiffOffset::new(hunk_overshoot.value); let old_end = old_start + DiffOffset::new(previous_expanded_summary.len); let new_start = DiffOffset::new(new_transforms.summary().diff_map.len); let new_end = new_start + diff_len; delta += diff_len.value as isize; let edit = Edit { old: old_start..old_end, new: new_start..new_end, }; edits.push(edit); } new_transforms.push( DiffTransform::DeletedHunk { base_text_byte_range: hunk.diff_base_byte_range.clone(), summary_including_newline: base_text_summary, buffer_id, base_text_start, needs_newline, }, &(), ); } if hunk_end_buffer_offset > hunk_start_buffer_offset { end_of_current_insert = excerpt_range.start + ExcerptOffset::new( hunk_end_buffer_offset - excerpt_buffer_range_start_offset, ); } if was_previously_expanded { cursor.next(&()); } } } } } while cursor.end(&()).0 <= edit.old.end { let Some(item) = cursor.item() else { break; }; if let DiffTransform::DeletedHunk { .. } = item { let old_range = cursor.start().1..cursor.end(&()).1; let new_offset = DiffOffset::new((old_range.start.value as isize + delta) as usize); delta -= (old_range.end - old_range.start).value as isize; let edit = Edit { old: old_range, new: new_offset..new_offset, }; edits.push(edit); } edit_old_end = cursor.start().1 + DiffOffset::new((edit.old.end - cursor.start().0).value); cursor.next(&()); } self.push_buffer_content_transform( &mut new_transforms, edit.new.end, end_of_current_insert, ); if let ChangeKind::InputEdited = operation { let edit_new_start = DiffOffset::new((edit_old_start.value as isize + delta) as usize); delta += (edit.new.end - edit.new.start).value as isize - (edit.old.end - edit.old.start).value as isize; let edit_new_end = DiffOffset::new((edit_old_end.value as isize + delta) as usize); let edit = DiffEdit { old: edit_old_start..edit_old_end, new: edit_new_start..edit_new_end, }; edits.push(edit); } if let Some((next_edit, _)) = changes.peek() { if next_edit.old.start < cursor.end(&()).0 { (edit, operation) = changes.next().unwrap(); continue; } } let suffix = cursor.end(&()).0 - edit.old.end; let transform_end = ExcerptOffset::new(new_transforms.summary().excerpt_map.len) + suffix; self.push_buffer_content_transform( &mut new_transforms, transform_end, end_of_current_insert, ); cursor.next(&()); break; } } self.append_transforms(&mut new_transforms, cursor.suffix(&())); self.edits_since_sync = self.edits_since_sync.compose(edits); drop(cursor); self.snapshot.transforms = new_transforms; self.snapshot.version += 1; #[cfg(test)] self.check_invariants(); } fn append_transforms( &self, new_transforms: &mut SumTree, subtree: SumTree, ) { if let Some(DiffTransform::BufferContent { is_inserted_hunk, summary, }) = subtree.first() { if self.extend_last_buffer_content_transform( new_transforms, *is_inserted_hunk, summary.clone(), ) { let mut cursor = subtree.cursor::<()>(&()); cursor.next(&()); cursor.next(&()); new_transforms.append(cursor.suffix(&()), &()); return; } } new_transforms.append(subtree, &()); } fn push_buffer_content_transform( &self, new_transforms: &mut SumTree, end_offset: ExcerptOffset, end_of_current_inserted_hunk: ExcerptOffset, ) { for (end_offset, region_is_inserted_hunk) in [ (end_offset.min(end_of_current_inserted_hunk), true), (end_offset, false), ] { let start_offset = ExcerptOffset::new(new_transforms.summary().excerpt_map.len); if end_offset <= start_offset { continue; } let summary_to_add = self .snapshot .excerpt_map_snapshot .text_summary_for_range::(start_offset..end_offset); if !self.extend_last_buffer_content_transform( new_transforms, region_is_inserted_hunk, summary_to_add.clone(), ) { new_transforms.push( DiffTransform::BufferContent { summary: summary_to_add, is_inserted_hunk: region_is_inserted_hunk, }, &(), ) } } } fn extend_last_buffer_content_transform( &self, new_transforms: &mut SumTree, region_is_inserted_hunk: bool, summary_to_add: TextSummary, ) -> bool { let mut did_extend = false; new_transforms.update_last( |last_transform| { if let DiffTransform::BufferContent { summary, is_inserted_hunk, } = last_transform { if *is_inserted_hunk == region_is_inserted_hunk { *summary += summary_to_add.clone(); did_extend = true; } } }, &(), ); did_extend } #[cfg(test)] fn check_invariants(&self) { let snapshot = &self.snapshot; if snapshot.transforms.summary().excerpt_map.len != snapshot.excerpt_map_snapshot.len().value { panic!( "incorrect input length. expected {}, got {}. transforms: {:+?}", snapshot.excerpt_map_snapshot.len(), snapshot.transforms.summary().excerpt_map.len, snapshot.transforms.items(&()), ); } let mut prev_transform: Option<&DiffTransform> = None; for item in snapshot.transforms.iter() { if let DiffTransform::BufferContent { summary, is_inserted_hunk, } = item { if let Some(DiffTransform::BufferContent { is_inserted_hunk: prev_is_inserted_hunk, .. }) = prev_transform { if *is_inserted_hunk == *prev_is_inserted_hunk { panic!( "multiple adjacent buffer content transforms with is_inserted_hunk = {is_inserted_hunk}. transforms: {:+?}", snapshot.transforms.items(&())); } } if summary.len == 0 && !snapshot.excerpt_map_snapshot.is_empty() { panic!("empty buffer content transform"); } } prev_transform = Some(item); } } } impl DiffMapSnapshot { pub fn diff_hunks_in_range<'a>( &'a self, range: Range, ) -> impl Iterator + 'a { let range = self.to_excerpt_offset(range.start)..self.to_excerpt_offset(range.end); self.excerpt_map_snapshot .excerpts_for_range(range.clone()) .filter_map(move |excerpt| { let buffer = excerpt.buffer(); let buffer_id = buffer.remote_id(); let diff = &self.diffs.get(&buffer_id)?.diff; let buffer_range = excerpt.map_range_to_buffer(range.clone()); let buffer_range = buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end); Some( diff.hunks_intersecting_range(buffer_range, excerpt.buffer()) .map(move |hunk| { let start = excerpt.map_point_from_buffer(Point::new(hunk.row_range.start, 0)); let end = excerpt.map_point_from_buffer(Point::new(hunk.row_range.end, 0)); MultiBufferDiffHunk { row_range: MultiBufferRow(start.row())..MultiBufferRow(end.row()), buffer_id, buffer_range: hunk.buffer_range.clone(), diff_base_byte_range: hunk.diff_base_byte_range.clone(), } }), ) }) .flatten() } pub fn diff_hunks_in_range_rev<'a>( &'a self, range: Range, ) -> impl Iterator + 'a { let range = self.to_excerpt_offset(range.start)..self.to_excerpt_offset(range.end); self.excerpt_map_snapshot .excerpts_for_range_rev(range.clone()) .filter_map(move |excerpt| { let buffer = excerpt.buffer(); let buffer_id = buffer.remote_id(); let diff = &self.diffs.get(&buffer_id)?.diff; let buffer_range = excerpt.map_range_to_buffer(range.clone()); let buffer_range = buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end); Some( diff.hunks_intersecting_range_rev(buffer_range, excerpt.buffer()) .map(move |hunk| { let start_row = excerpt .map_point_from_buffer(Point::new(hunk.row_range.start, 0)) .row(); let end_row = excerpt .map_point_from_buffer(Point::new(hunk.row_range.end, 0)) .row(); MultiBufferDiffHunk { row_range: MultiBufferRow(start_row)..MultiBufferRow(end_row), buffer_id, buffer_range: hunk.buffer_range.clone(), diff_base_byte_range: hunk.diff_base_byte_range.clone(), } }), ) }) .flatten() } pub fn has_diff_hunks(&self) -> bool { self.diffs.values().any(|diff| !diff.diff.is_empty()) } #[cfg(any(test, feature = "test-support"))] pub fn text(&self) -> String { self.chunks(DiffOffset::zero()..self.len(), false) .map(|c| c.text) .collect() } pub fn len(&self) -> DiffOffset { DiffOffset::new(self.transforms.summary().diff_map.len) } pub fn max_point(&self) -> DiffPoint { DiffPoint::wrap(self.transforms.summary().diff_map.lines) } pub fn text_summary(&self) -> TextSummary { self.transforms.summary().diff_map.clone() } pub fn text_summary_for_range(&self, range: Range) -> TextSummary { let mut cursor = self.transforms.cursor::<(DiffOffset, ExcerptOffset)>(&()); cursor.seek(&range.start, Bias::Right, &()); let Some(first_transform) = cursor.item() else { return TextSummary::default(); }; let (diff_transform_start, multibuffer_transform_start) = cursor.start().clone(); let (diff_transform_end, _) = cursor.end(&()); let diff_start = range.start; let diff_end = std::cmp::min(range.end, diff_transform_end); let mut result = match first_transform { DiffTransform::BufferContent { .. } => { let multibuffer_start = multibuffer_transform_start + ExcerptOffset::new((diff_start - diff_transform_start).value); let multibuffer_end = multibuffer_transform_start + ExcerptOffset::new((diff_end - diff_transform_start).value); self.excerpt_map_snapshot .text_summary_for_range(multibuffer_start..multibuffer_end) } DiffTransform::DeletedHunk { buffer_id, base_text_byte_range, needs_newline, .. } => { let buffer_start = base_text_byte_range.start + (diff_start - diff_transform_start).value; let mut buffer_end = base_text_byte_range.start + (diff_end - diff_transform_start).value; let Some(buffer_diff) = self.diffs.get(buffer_id) else { panic!("{:?} is in non-extant deleted hunk", range.start) }; if *needs_newline && diff_end == diff_transform_end { buffer_end -= 1; } let mut summary = buffer_diff .base_text .text_summary_for_range::(buffer_start..buffer_end); if *needs_newline && diff_end == diff_transform_end { summary.add_newline(); } summary } }; if range.end < diff_transform_end { return result; } cursor.next(&()); result = result + cursor.summary(&range.end, Bias::Right, &()); let Some(last_transform) = cursor.item() else { return result; }; let (diff_transform_start, multibuffer_transform_start) = cursor.start().clone(); result += match last_transform { DiffTransform::BufferContent { .. } => { let multibuffer_end = multibuffer_transform_start + ExcerptOffset::new((range.end - diff_transform_start).value); self.excerpt_map_snapshot .text_summary_for_range::( multibuffer_transform_start..multibuffer_end, ) } DiffTransform::DeletedHunk { base_text_byte_range, buffer_id, needs_newline, .. } => { let buffer_end = base_text_byte_range.start + (range.end - diff_transform_start).value; let Some(buffer_diff) = self.diffs.get(buffer_id) else { panic!("{:?} is in non-extant deleted hunk", range.end) }; let mut result = buffer_diff .base_text .text_summary_for_range::( base_text_byte_range.start..buffer_end, ); if *needs_newline && buffer_end == base_text_byte_range.end + 1 { result.add_newline(); } result } }; result } pub fn offset_to_point(&self, offset: DiffOffset) -> DiffPoint { let mut cursor = self.transforms.cursor::(&()); cursor.seek(&offset, Bias::Right, &()); let start_transform = cursor.start(); let overshoot = offset - start_transform.diff_offset(); if overshoot.value == 0 { return start_transform.diff_point(); } match cursor.item() { Some(DiffTransform::BufferContent { .. }) => { let multibuffer_offset = start_transform.multibuffer_offset() + ExcerptOffset::new(overshoot.value); let multibuffer_point = self .excerpt_map_snapshot .offset_to_point(multibuffer_offset); start_transform.diff_point() + DiffPoint::wrap((multibuffer_point - start_transform.excerpt_point()).value) } Some(DiffTransform::DeletedHunk { buffer_id, base_text_start, base_text_byte_range, .. }) => { let Some(buffer_diff) = self.diffs.get(buffer_id) else { panic!("{:?} is in non-extant deleted hunk", offset) }; let buffer_offset = base_text_byte_range.start + overshoot.value; let buffer_point = buffer_diff.base_text.offset_to_point(buffer_offset); start_transform.diff_point() + DiffPoint::wrap(buffer_point - base_text_start) } None => { panic!("{:?} is past end of buffer", offset) } } } pub fn clip_point(&self, point: DiffPoint, bias: Bias) -> DiffPoint { let mut cursor = self.transforms.cursor::(&()); cursor.seek(&point, Bias::Right, &()); let start_transform = cursor.start(); let overshoot = point - start_transform.diff_point(); if overshoot.value.is_zero() { return start_transform.diff_point(); } match cursor.item() { Some(DiffTransform::BufferContent { .. }) => { let excerpt_point = start_transform.excerpt_point() + ExcerptPoint::wrap(overshoot.value); let clipped = self.excerpt_map_snapshot.clip_point(excerpt_point, bias); start_transform.diff_point() + DiffPoint::wrap((clipped - start_transform.excerpt_point()).value) } Some(DiffTransform::DeletedHunk { buffer_id, base_text_start, .. }) => { let Some(buffer_diff) = self.diffs.get(buffer_id) else { panic!("{:?} is in non-extant deleted hunk", point) }; let buffer_point = *base_text_start + overshoot.value; let clipped = buffer_diff.base_text.clip_point(buffer_point, bias); start_transform.diff_point() + DiffPoint::wrap(clipped - base_text_start) } None => cursor.end(&()).diff_point(), } } pub fn point_to_offset(&self, point: DiffPoint) -> DiffOffset { let mut cursor = self.transforms.cursor::(&()); cursor.seek(&point, Bias::Right, &()); let start_transform = cursor.start(); let overshoot = point - start_transform.diff_point(); if overshoot.is_zero() { return start_transform.diff_offset(); } match cursor.item() { Some(DiffTransform::BufferContent { .. }) => { let excerpt_point = start_transform.excerpt_point() + ExcerptPoint::wrap(overshoot.value); let multibuffer_offset = self.excerpt_map_snapshot.point_to_offset(excerpt_point); start_transform.diff_offset() + DiffOffset::new( (multibuffer_offset - start_transform.multibuffer_offset()).value, ) } Some(DiffTransform::DeletedHunk { buffer_id, base_text_start, base_text_byte_range, .. }) => { let Some(buffer_diff) = self.diffs.get(buffer_id) else { panic!("{:?} is in non-extant deleted hunk", point) }; let buffer_point = *base_text_start + overshoot.value; let buffer_offset = buffer_diff.base_text.point_to_offset(buffer_point); start_transform.diff_offset() + DiffOffset::new(buffer_offset - base_text_byte_range.start) } None => { panic!("{:?} is past end of buffer", point) } } } // pub fn point_to_anchor(&self, point: DiffPoint, bias: Bias) -> DisplayAnchor { // let mut cursor = self.transforms.cursor::<(DiffPoint, Point)>(&()); // cursor.seek(&point, Bias::Right, &()); // let (diff_start, multibuffer_start) = *cursor.start(); // match cursor.item() { // Some(DiffTransform::BufferContent { .. }) => { // let multibuffer_point = multibuffer_start + (point - diff_start).0; // DisplayAnchor { // anchor: self.buffer.anchor_at(multibuffer_point, bias), // diff_base_anchor: None, // } // } // Some(DiffTransform::DeletedHunk { // buffer_id, // base_text_start, // .. // }) => { // let diff_base_point = *base_text_start + (point - diff_start).0; // let diff_base_anchor = if let Some(diff_base_snapshot) = self.diffs.get(&buffer_id) // { // Some( // diff_base_snapshot // .base_text // .anchor_at(diff_base_point, bias), // ) // } else { // debug_panic!("{} is missing diff base", buffer_id); // None // }; // DisplayAnchor { // anchor: self.buffer.anchor_at(multibuffer_start, Bias::Left), // diff_base_anchor, // } // } // None => { // panic!("{:?} is out of range", point) // } // } // } // pub fn anchor_to_point(&self, anchor: DisplayAnchor) -> DiffPoint { // let multibuffer_point = anchor.anchor.to_point(&self.buffer); // let mut cursor = self.transforms.cursor::<(Point, DiffPoint)>(&()); // cursor.seek(&multibuffer_point, Bias::Left, &()); // if let Some(DiffTransform::DeletedHunk { // buffer_id, // base_text_start, // .. // }) = cursor.item() // { // if let Some(diff_base_anchor) = anchor.diff_base_anchor { // if let Some(diff_base_snapshot) = self.diffs.get(&buffer_id) { // if diff_base_anchor.buffer_id == Some(diff_base_snapshot.base_text.remote_id()) // { // let (_, diff_start) = *cursor.start(); // let base_text_point = // diff_base_anchor.to_point(&diff_base_snapshot.base_text); // return diff_start + DiffPoint(base_text_point - base_text_start); // } // } // } else { // cursor.next(&()) // } // } // debug_assert!(matches!( // cursor.item(), // Some(DiffTransform::BufferContent { .. }) // )); // let (multibuffer_start, diff_start) = *cursor.start(); // diff_start + DiffPoint(multibuffer_point - multibuffer_start) // } // pub fn compare_anchors(&self, a: &DisplayAnchor, b: &DisplayAnchor) -> std::cmp::Ordering { // let buffer_cmp = a.anchor.cmp(&b.anchor, self.buffer()); // if buffer_cmp != std::cmp::Ordering::Equal { // return buffer_cmp; // } // match (a.diff_base_anchor.is_some(), b.diff_base_anchor.is_some()) { // (false, true) => return std::cmp::Ordering::Greater, // (true, false) => return std::cmp::Ordering::Less, // (false, false) => return std::cmp::Ordering::Equal, // (true, true) => {} // } // let diff_anchor_a = a.diff_base_anchor.unwrap(); // let diff_anchor_b = b.diff_base_anchor.unwrap(); // if diff_anchor_a.buffer_id != diff_anchor_b.buffer_id || diff_anchor_a.buffer_id.is_none() { // return std::cmp::Ordering::Equal; // } // let Some(diff_base_snapshot) = self.diffs.get(&diff_anchor_a.buffer_id.unwrap()) else { // return std::cmp::Ordering::Equal; // }; // diff_anchor_a.cmp(&diff_anchor_b, &diff_base_snapshot.base_text) // } pub fn to_excerpt_offset(&self, offset: DiffOffset) -> ExcerptOffset { let mut cursor = self.transforms.cursor::<(DiffOffset, ExcerptOffset)>(&()); cursor.seek(&offset, Bias::Right, &()); let mut excerpt_offset = cursor.start().1; if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { let overshoot = offset - cursor.start().0; excerpt_offset.value += overshoot.value; } excerpt_offset } pub fn to_excerpt_point(&self, point: DiffPoint) -> ExcerptPoint { let mut cursor = self.transforms.cursor::<(DiffPoint, ExcerptPoint)>(&()); cursor.seek(&point, Bias::Right, &()); let mut excerpt_point = cursor.start().1; if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { let overshoot = point - cursor.start().0; excerpt_point.value += overshoot.value; } excerpt_point } pub fn to_diff_offset(&self, excerpt_offset: ExcerptOffset) -> DiffOffset { let mut cursor = self.transforms.cursor::<(ExcerptOffset, DiffOffset)>(&()); cursor.seek(&excerpt_offset, Bias::Right, &()); let mut diff_offset = cursor.start().1; if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { let overshoot = excerpt_offset - cursor.start().0; diff_offset.value += overshoot.value; } diff_offset } pub fn to_diff_point(&self, excerpt_point: ExcerptPoint) -> DiffPoint { let mut cursor = self.transforms.cursor::<(ExcerptPoint, DiffPoint)>(&()); cursor.seek(&excerpt_point, Bias::Right, &()); let mut diff_point = cursor.start().1; if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { let overshoot = excerpt_point - cursor.start().0; diff_point.value += overshoot.value; } diff_point } pub(crate) fn chunks<'a>( &'a self, range: Range, language_aware: bool, ) -> DiffMapChunks<'a> { let mut cursor = self.transforms.cursor::<(DiffOffset, ExcerptOffset)>(&()); cursor.seek(&range.end, Bias::Right, &()); let mut excerpt_end = cursor.start().1; if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { let overshoot = range.end - cursor.start().0; excerpt_end.value += overshoot.value; } cursor.seek(&range.start, Bias::Right, &()); let mut excerpt_start = cursor.start().1; if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { let overshoot = range.start - cursor.start().0; excerpt_start.value += overshoot.value; } let excerpt_chunks = self .excerpt_map_snapshot .chunks(excerpt_start..excerpt_end, language_aware); DiffMapChunks { snapshot: self, language_aware, cursor, current_chunk: None, excerpt_map_chunks: excerpt_chunks, excerpt_map_offset: excerpt_start, offset: range.start, diff_base_chunks: None, end_offset: range.end, } } pub fn row_infos(&self, start_row: u32) -> DiffMapRows { if start_row > self.transforms.summary().diff_map.lines.row { panic!("invalid diff map row {}", start_row); } let diff_point = DiffPoint::new(start_row, 0); let mut cursor = self.transforms.cursor::<(DiffPoint, ExcerptPoint)>(&()); cursor.seek(&diff_point, Bias::Right, &()); let (diff_transform_start, buffer_transform_start) = cursor.start().clone(); let overshoot = if matches!(cursor.item(), Some(DiffTransform::BufferContent { .. })) { diff_point.row() - diff_transform_start.row() } else { 0 }; let input_buffer_rows = self .excerpt_map_snapshot .row_infos(ExcerptRow::new(buffer_transform_start.row() + overshoot)); DiffMapRows { diff_point, input_buffer_rows, cursor, } } } impl<'a> DiffMapChunks<'a> { pub fn seek(&mut self, range: Range) { self.cursor.seek(&range.end, Bias::Right, &()); let mut excerpt_map_end = self.cursor.start().1; if let Some(DiffTransform::BufferContent { .. }) = self.cursor.item() { let overshoot = range.end - self.cursor.start().0; excerpt_map_end.value += overshoot.value; } self.cursor.seek(&range.start, Bias::Right, &()); let mut excerpt_map_start = self.cursor.start().1; if let Some(DiffTransform::BufferContent { .. }) = self.cursor.item() { let overshoot = range.start - self.cursor.start().0; excerpt_map_start.value += overshoot.value; } self.excerpt_map_chunks .seek(excerpt_map_start..excerpt_map_end); self.current_chunk.take(); self.excerpt_map_offset = excerpt_map_start; self.offset = range.start; self.end_offset = range.end; } } impl<'a> Iterator for DiffMapChunks<'a> { type Item = Chunk<'a>; fn next(&mut self) -> Option { if self.offset >= self.end_offset { return None; } if self.offset == self.cursor.end(&()).0 { self.cursor.next(&()); } let transform = self.cursor.item()?; match transform { DiffTransform::BufferContent { summary, .. } => { let chunk = self .current_chunk .get_or_insert_with(|| self.excerpt_map_chunks.next().unwrap()); let chunk_end = self.offset + DiffOffset::new(chunk.text.len()); let mut transform_end = self.cursor.start().0 + DiffOffset::new(summary.len); if transform_end > self.end_offset { transform_end = self.end_offset } if transform_end < chunk_end { let (before, after) = chunk.text.split_at(transform_end.value - self.offset.value); self.offset = transform_end; chunk.text = after; Some(Chunk { text: before, ..chunk.clone() }) } else { self.offset = chunk_end; self.current_chunk.take() } } DiffTransform::DeletedHunk { buffer_id, base_text_byte_range, needs_newline, .. } => { let hunk_start_offset = self.cursor.start().0; let base_text_start_offset = base_text_byte_range.start; let base_text_end_offset = base_text_byte_range.end; let diff_base_end_offset = base_text_end_offset .min(base_text_start_offset + self.end_offset.value - hunk_start_offset.value); let diff_base_start_offset = base_text_start_offset + self.offset.value - hunk_start_offset.value; let mut chunks = if let Some((_, mut chunks)) = self .diff_base_chunks .take() .filter(|(id, _)| id == buffer_id) { if chunks.offset() != diff_base_start_offset { chunks.seek(diff_base_start_offset..diff_base_end_offset); } chunks } else { let base_buffer = &self.snapshot.diffs.get(&buffer_id)?.base_text; base_buffer.chunks( diff_base_start_offset..diff_base_end_offset, self.language_aware, ) }; let chunk = if let Some(chunk) = chunks.next() { self.offset.value += chunk.text.len(); self.diff_base_chunks = Some((*buffer_id, chunks)); chunk } else { debug_assert!(needs_newline); self.offset.value += "\n".len(); Chunk { text: "\n", ..Default::default() } }; Some(chunk) } } } } impl<'a> DiffMapRows<'a> { pub fn seek(&mut self, row: u32) { self.diff_point = DiffPoint::new(row, 0); self.cursor.seek(&self.diff_point, Bias::Right, &()); let (diff_transform_start, inlay_transform_start) = self.cursor.start().clone(); let overshoot = if matches!( self.cursor.item(), Some(DiffTransform::BufferContent { .. }) ) { self.diff_point.row() - diff_transform_start.row() } else { 0 }; self.input_buffer_rows .seek(ExcerptRow::new(inlay_transform_start.row() + overshoot)); } } impl<'a> Iterator for DiffMapRows<'a> { type Item = RowInfo; fn next(&mut self) -> Option { let result = match self.cursor.item() { Some(DiffTransform::DeletedHunk { .. }) => Some(RowInfo { buffer_row: None, diff_status: Some(DiffHunkStatus::Removed), }), Some(DiffTransform::BufferContent { is_inserted_hunk, .. }) => { let row = self.input_buffer_rows.next(); row.map(|row| RowInfo { buffer_row: row, diff_status: if *is_inserted_hunk { Some(DiffHunkStatus::Added) } else { None }, }) } None => self.input_buffer_rows.next().map(|row| RowInfo { buffer_row: row, diff_status: None, }), }; self.diff_point.value += Point::new(1, 0); if self.diff_point >= self.cursor.end(&()).0 { self.cursor.next(&()); } result } } impl sum_tree::Item for DiffTransform { type Summary = DiffTransformSummary; fn summary(&self, _: &::Context) -> Self::Summary { match self { DiffTransform::BufferContent { summary, .. } => DiffTransformSummary { excerpt_map: summary.clone(), diff_map: summary.clone(), }, DiffTransform::DeletedHunk { summary_including_newline: summary, .. } => DiffTransformSummary { excerpt_map: TextSummary::default(), diff_map: summary.clone(), }, } } } impl sum_tree::Summary for DiffTransformSummary { type Context = (); fn zero(_: &Self::Context) -> Self { DiffTransformSummary { excerpt_map: TextSummary::default(), diff_map: TextSummary::default(), } } fn add_summary(&mut self, summary: &Self, _: &Self::Context) { self.excerpt_map += &summary.excerpt_map; self.diff_map += &summary.diff_map; } } impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for ExcerptOffset { fn zero(_: &()) -> Self { Default::default() } fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { self.value += summary.excerpt_map.len } } impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for DiffOffset { fn zero(_: &()) -> Self { Default::default() } fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { self.value += summary.diff_map.len } } impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for ExcerptPoint { fn zero(_: &()) -> Self { Default::default() } fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { self.value += summary.excerpt_map.lines } } impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for DiffPoint { fn zero(_: &()) -> Self { Default::default() } fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { self.value += summary.diff_map.lines } } impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for TextSummary { fn zero(_cx: &()) -> Self { Default::default() } fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { *self += &summary.diff_map; } } impl<'a> sum_tree::SeekTarget<'a, DiffTransformSummary, DiffTransformSummary> for DiffPoint { fn cmp( &self, cursor_location: &DiffTransformSummary, _: &::Context, ) -> std::cmp::Ordering { Ord::cmp(self, &cursor_location.diff_point()) } } impl<'a> sum_tree::SeekTarget<'a, DiffTransformSummary, DiffTransformSummary> for DiffOffset { fn cmp( &self, cursor_location: &DiffTransformSummary, _: &::Context, ) -> std::cmp::Ordering { Ord::cmp(self, &cursor_location.diff_offset()) } } // #[cfg(test)] // mod tests { // use super::*; // use gpui::{AppContext, TestAppContext}; // use indoc::indoc; // use language::Buffer; // use multi_buffer::{Anchor, MultiBuffer}; // use project::Project; // use settings::SettingsStore; // use text::OffsetUtf16; // #[gpui::test] // fn test_basic_diff_map_updates(cx: &mut TestAppContext) { // cx.update(init_test); // let text = indoc!( // " // ZERO // one // TWO // three // six // " // ); // let base_text = indoc!( // " // one // two // three // four // five // six // " // ); // let (diff_map, mut snapshot, mut deps) = build_diff_map(text, Some(base_text), cx); // assert_eq!( // snapshot.text(), // indoc!( // " // ZERO // one // TWO // three // six // " // ), // ); // diff_map.update(cx, |diff_map, cx| { // diff_map.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) // }); // let sync = diff_map.update(cx, |diff_map, cx| { // diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) // }); // assert_new_snapshot( // &mut snapshot, // sync, // indoc!( // " // + ZERO // one // - two // + TWO // three // - four // - five // six // " // ), // ); // assert_eq!( // snapshot // .row_infos(0) // .map(|info| info.buffer_row) // .collect::>(), // vec![ // Some(0), // Some(1), // None, // Some(2), // Some(3), // None, // None, // Some(4), // Some(5) // ] // ); // assert_chunks_in_ranges(&snapshot); // assert_consistent_line_numbers(&snapshot); // for (point, offset) in &[ // ( // DiffPoint::new(0, 0), // DiffOffset(snapshot.text().find("ZERO").unwrap()), // ), // ( // DiffPoint::new(2, 2), // DiffOffset(snapshot.text().find("two").unwrap() + 2), // ), // ( // DiffPoint::new(4, 3), // DiffOffset(snapshot.text().find("three").unwrap() + 3), // ), // (DiffPoint::new(8, 0), DiffOffset(snapshot.text().len())), // ] { // let actual = snapshot.point_to_offset(*point); // assert_eq!(actual, *offset, "for {:?}", point); // let actual = snapshot.offset_to_point(*offset); // assert_eq!(actual, *point, "for {:?}", offset); // } // diff_map.update(cx, |diff_map, cx| { // diff_map.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) // }); // let sync = diff_map.update(cx, |diff_map, cx| { // diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) // }); // assert_new_snapshot( // &mut snapshot, // sync, // indoc!( // " // ZERO // one // TWO // three // six // " // ), // ); // // Expand the first diff hunk // diff_map.update(cx, |diff_map, cx| { // let position = deps.multibuffer_snapshot.anchor_before(Point::new(2, 0)); // diff_map.expand_diff_hunks(vec![position..position], cx) // }); // let sync = diff_map.update(cx, |diff_map, cx| { // diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) // }); // assert_new_snapshot( // &mut snapshot, // sync, // indoc!( // " // ZERO // one // - two // + TWO // three // six // " // ), // ); // // Expand the second diff hunk // diff_map.update(cx, |diff_map, cx| { // let position = deps.multibuffer_snapshot.anchor_before(Point::new(3, 0)); // diff_map.expand_diff_hunks(vec![position..position], cx) // }); // let sync = diff_map.update(cx, |diff_map, cx| { // diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) // }); // assert_new_snapshot( // &mut snapshot, // sync, // indoc!( // " // ZERO // one // - two // + TWO // three // - four // - five // six // " // ), // ); // // Edit the buffer before the first hunk // let edits = deps.update_buffer(cx, |buffer, cx| { // buffer.edit_via_marked_text( // indoc!( // " // ZERO // one« hundred // thousand» // TWO // three // six // " // ), // None, // cx, // ); // }); // let sync = diff_map.update(cx, |diff_map, cx| { // diff_map.sync(deps.multibuffer_snapshot.clone(), edits, cx) // }); // assert_new_snapshot( // &mut snapshot, // sync, // indoc!( // " // ZERO // one hundred // thousand // - two // + TWO // three // - four // - five // six // " // ), // ); // // Recalculate the diff, changing the first diff hunk. // let _ = deps.change_set.update(cx, |change_set, cx| { // change_set.recalculate_diff(deps.buffer.read(cx).text_snapshot(), cx) // }); // cx.run_until_parked(); // let sync = diff_map.update(cx, |diff_map, cx| { // diff_map.sync(deps.multibuffer_snapshot.clone(), Vec::new(), cx) // }); // assert_new_snapshot( // &mut snapshot, // sync, // indoc!( // " // ZERO // one hundred // thousand // TWO // three // - four // - five // six // " // ), // ); // } // #[gpui::test] // fn test_diff_map_multiple_buffer_edits(cx: &mut TestAppContext) { // cx.update(init_test); // let text = "hello world"; // let (diff_map, mut snapshot, mut deps) = build_diff_map(text, None, cx); // assert_eq!(snapshot.text(), "hello world"); // let edits = deps.update_buffer(cx, |buffer, cx| { // buffer.edit([(4..5, "a"), (9..11, "k")], None, cx); // }); // let sync = diff_map.update(cx, |diff_map, cx| { // diff_map.sync(deps.multibuffer_snapshot, edits, cx) // }); // assert_new_snapshot(&mut snapshot, sync, indoc!("hella work")); // } // #[gpui::test] // fn test_diff_map_clipping(cx: &mut TestAppContext) { // cx.update(init_test); // let text = indoc!( // "₀ // ₃ // ₄ // ₅ // " // ); // let base_text = indoc!( // "₀ // ₁ // ₂ // ₃ // ₄ // " // ); // let (diff_map, mut diff_snapshot, deps) = build_diff_map(text, Some(base_text), cx); // diff_map.update(cx, |diff_map, cx| diff_map.set_all_hunks_expanded(cx)); // cx.run_until_parked(); // let sync = diff_map.update(cx, |diff_map, cx| { // diff_map.sync(deps.multibuffer_snapshot, vec![], cx) // }); // assert_new_snapshot( // &mut diff_snapshot, // sync, // indoc! {" // ₀ // - ₁ // - ₂ // ₃ // ₄ // + ₅ // "}, // ); // for (point, (left, right)) in [ // ( // DiffPoint::new(0, 0), // start // (DiffPoint::new(0, 0), DiffPoint::new(0, 0)), // ), // ( // DiffPoint::new(1, 1), // deleted // (DiffPoint::new(1, 0), DiffPoint::new(1, 3)), // ), // ( // DiffPoint::new(3, 1), // unchanged // (DiffPoint::new(3, 0), DiffPoint::new(3, 3)), // ), // ( // DiffPoint::new(5, 2), // inserted // (DiffPoint::new(5, 0), DiffPoint::new(5, 3)), // ), // ( // DiffPoint::new(6, 0), // end // (DiffPoint::new(6, 0), DiffPoint::new(6, 0)), // ), // ( // DiffPoint::new(7, 7), // beyond // (DiffPoint::new(6, 0), DiffPoint::new(6, 0)), // ), // ] { // assert_eq!(left, diff_snapshot.clip_point(point, Bias::Left)); // assert_eq!(right, diff_snapshot.clip_point(point, Bias::Right)); // } // assert_eq!( // diff_snapshot.text_summary_for_range(DiffOffset(0)..DiffOffset(0)), // TextSummary::default() // ); // assert_eq!( // diff_snapshot.text_summary_for_range(diff_snapshot.len()..diff_snapshot.len()), // TextSummary::default() // ); // let full_summary = TextSummary { // len: 24, // len_utf16: OffsetUtf16(12), // lines: Point { row: 6, column: 0 }, // first_line_chars: 1, // last_line_chars: 0, // last_line_len_utf16: 0, // longest_row: 0, // longest_row_chars: 1, // }; // let partial_summary = TextSummary { // len: 8, // len_utf16: OffsetUtf16(4), // lines: Point { row: 2, column: 0 }, // first_line_chars: 1, // last_line_chars: 0, // last_line_len_utf16: 0, // longest_row: 0, // longest_row_chars: 1, // }; // let two = DiffOffset(diff_snapshot.text().find("₂").unwrap()); // let four = DiffOffset(diff_snapshot.text().find("₄").unwrap()); // assert_eq!( // diff_snapshot.text_summary_for_range(DiffOffset(0)..diff_snapshot.len()), // full_summary // ); // assert_eq!( // diff_snapshot.text_summary_for_range(DiffOffset(0)..two), // partial_summary // ); // assert_eq!( // diff_snapshot.text_summary_for_range(two..four), // partial_summary // ); // assert_eq!( // diff_snapshot.text_summary_for_range(four..diff_snapshot.len()), // partial_summary // ); // } // #[gpui::test] // fn test_empty_diff_map(cx: &mut TestAppContext) { // cx.update(init_test); // let (_diff_map, diff_snapshot, _deps) = build_diff_map("", None, cx); // assert_eq!( // diff_snapshot // .row_infos(0) // .map(|info| info.buffer_row) // .collect::>(), // [Some(0)] // ); // } // #[gpui::test] // fn test_expand_no_newline(cx: &mut TestAppContext) { // cx.update(init_test); // let base_text = "todo"; // let text = indoc!( // " // one // two // " // ); // let (diff_map, mut diff_snapshot, deps) = build_diff_map(text, Some(base_text), cx); // assert_eq!(diff_snapshot.text(), "one\ntwo\n"); // assert_eq!( // diff_snapshot // .row_infos(0) // .map(|info| info.buffer_row) // .collect::>(), // [Some(0), Some(1), Some(2)] // ); // diff_map.update(cx, |diff_map, cx| { // diff_map.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) // }); // let sync = diff_map.update(cx, |diff_map, cx| { // diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) // }); // assert_new_snapshot( // &mut diff_snapshot, // sync, // indoc!( // " // - todo // + one // + two // " // ), // ); // assert_eq!(diff_snapshot.text(), "todo\none\ntwo\n"); // assert_eq!( // diff_snapshot // .row_infos(0) // .map(|info| info.buffer_row) // .collect::>(), // [None, Some(0), Some(1), Some(2)] // ); // assert_eq!( // diff_snapshot // .row_infos(1) // .map(|info| info.buffer_row) // .collect::>(), // [Some(0), Some(1), Some(2)] // ); // for (point, offset) in [ // (DiffPoint::new(0, 4), DiffOffset(4)), // (DiffPoint::new(1, 0), DiffOffset(5)), // (DiffPoint::new(1, 1), DiffOffset(6)), // ] { // assert_eq!(diff_snapshot.offset_to_point(offset), point); // assert_eq!(diff_snapshot.point_to_offset(point), offset); // } // for point in [ // DiffPoint::new(0, 4), // DiffPoint::new(1, 0), // DiffPoint::new(1, 1), // ] { // let anchor = diff_snapshot.point_to_anchor(point, Bias::Left); // dbg!(&anchor); // let actual = diff_snapshot.anchor_to_point(anchor); // assert_eq!(point, actual); // } // assert_eq!( // diff_snapshot.clip_point(DiffPoint::new(0, 5), Bias::Left), // DiffPoint::new(0, 4) // ); // assert_eq!( // diff_snapshot.to_diff_point(Point::new(0, 0)), // DiffPoint::new(1, 0) // ); // assert_eq!( // diff_snapshot.to_multibuffer_point(DiffPoint::new(0, 4)), // Point::new(0, 0) // ); // } // #[gpui::test] // fn test_expand_collapse_at_positions_adjacent_to_hunks(cx: &mut TestAppContext) { // cx.update(init_test); // let base_text = indoc!( // " // one // two // three // four // five // six // seven // eight // " // ); // let text = indoc!( // " // one // two // five // six; seven // eight // " // ); // let (diff_map, mut snapshot, deps) = build_diff_map(text, Some(base_text), cx); // // Expand at the line right below a deleted hunk. // diff_map.update(cx, |diff_map, cx| { // let point = deps.multibuffer_snapshot.anchor_before(Point::new(2, 0)); // diff_map.expand_diff_hunks(vec![point..point], cx) // }); // let sync = diff_map.update(cx, |diff_map, cx| { // diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) // }); // assert_new_snapshot( // &mut snapshot, // sync, // indoc!( // " // one // two // - three // - four // five // six; seven // eight // " // ), // ); // // Collapse at the line right below a deleted hunk. // diff_map.update(cx, |diff_map, cx| { // let point = deps.multibuffer_snapshot.anchor_before(Point::new(2, 0)); // diff_map.collapse_diff_hunks(vec![point..point], cx) // }); // let sync = diff_map.update(cx, |diff_map, cx| { // diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) // }); // assert_new_snapshot( // &mut snapshot, // sync, // indoc!( // " // one // two // five // six; seven // eight // " // ), // ); // // Expand at the line right above a deleted hunk. // diff_map.update(cx, |diff_map, cx| { // let point = deps.multibuffer_snapshot.anchor_before(Point::new(1, 0)); // diff_map.expand_diff_hunks(vec![point..point], cx) // }); // let sync = diff_map.update(cx, |diff_map, cx| { // diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) // }); // assert_new_snapshot( // &mut snapshot, // sync, // indoc!( // " // one // two // - three // - four // five // six; seven // eight // " // ), // ); // eprintln!(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); // // Expand at the line right below a modified hunk. Should not expand anything. // diff_map.update(cx, |diff_map, cx| { // let point = deps.multibuffer_snapshot.anchor_before(Point::new(4, 0)); // diff_map.expand_diff_hunks(vec![point..point], cx) // }); // let sync = diff_map.update(cx, |diff_map, cx| { // diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) // }); // assert_new_snapshot( // &mut snapshot, // sync, // indoc!( // " // one // two // - three // - four // five // six; seven // eight // " // ), // ); // } // #[gpui::test] // fn test_expand_collapse_insertion_hunk(cx: &mut TestAppContext) { // cx.update(init_test); // let base_text = indoc!( // " // one // two // seven // eight // " // ); // let text = indoc!( // " // one // two // three // four // five // six // seven // eight // " // ); // let (diff_map, mut snapshot, deps) = build_diff_map(text, Some(base_text), cx); // // Expand at the line right right after a deleted hunk. // diff_map.update(cx, |diff_map, cx| { // let point = deps.multibuffer_snapshot.anchor_before(Point::new(2, 0)); // diff_map.expand_diff_hunks(vec![point..point], cx) // }); // let sync = diff_map.update(cx, |diff_map, cx| { // diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) // }); // assert_new_snapshot( // &mut snapshot, // sync, // indoc!( // " // one // two // + three // + four // + five // + six // seven // eight // " // ), // ); // diff_map.update(cx, |diff_map, cx| { // let point = deps.multibuffer_snapshot.anchor_before(Point::new(2, 0)); // diff_map.collapse_diff_hunks(vec![point..point], cx) // }); // let sync = diff_map.update(cx, |diff_map, cx| { // diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) // }); // assert_new_snapshot( // &mut snapshot, // sync, // indoc!( // " // one // two // three // four // five // six // seven // eight // " // ), // ); // } // #[gpui::test] // fn test_edit_in_insertion_hunk(cx: &mut TestAppContext) { // cx.update(init_test); // let base_text = indoc!( // " // one // two // six // seven // " // ); // let text = indoc!( // " // one // two // three // four // five // six // seven // " // ); // let (diff_map, mut snapshot, mut deps) = build_diff_map(text, Some(base_text), cx); // // Expand the hunk // diff_map.update(cx, |diff_map, cx| { // diff_map.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) // }); // let sync = diff_map.update(cx, |diff_map, cx| { // diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) // }); // assert_new_snapshot( // &mut snapshot, // sync, // indoc!( // " // one // two // + three // + four // + five // six // seven // " // ), // ); // eprintln!(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); // let edits = deps.update_buffer(cx, |buffer, cx| { // buffer.edit_via_marked_text( // indoc!( // " // one // two // three« // !» // four // five // six // seven // " // ), // None, // cx, // ) // }); // let sync = diff_map.update(cx, |diff_map, cx| { // diff_map.sync(deps.multibuffer_snapshot.clone(), edits, cx) // }); // assert_new_snapshot( // &mut snapshot, // sync, // indoc!( // " // one // two // + three // + ! // + four // + five // six // seven // " // ), // ); // } // // #[track_caller] // fn assert_new_snapshot( // snapshot: &mut DiffMapSnapshot, // (new_snapshot, edits): (DiffMapSnapshot, Vec>), // expected_diff: &str, // ) { // let actual_text = new_snapshot.text(); // let line_infos = new_snapshot.row_infos(0).collect::>(); // let has_diff = line_infos.iter().any(|info| info.diff_status.is_some()); // let actual_diff = actual_text // .split('\n') // .zip(line_infos) // .map(|(line, info)| { // let marker = match info.diff_status { // Some(DiffHunkStatus::Added) => "+ ", // Some(DiffHunkStatus::Removed) => "- ", // Some(DiffHunkStatus::Modified) => unreachable!(), // None => { // if has_diff { // " " // } else { // "" // } // } // }; // if line.is_empty() { // String::new() // } else { // format!("{marker}{line}") // } // }) // .collect::>() // .join("\n"); // pretty_assertions::assert_eq!(actual_diff, expected_diff); // check_edits(snapshot, &new_snapshot, &edits); // *snapshot = new_snapshot; // } // #[track_caller] // fn check_edits( // old_snapshot: &DiffMapSnapshot, // new_snapshot: &DiffMapSnapshot, // edits: &[DiffEdit], // ) { // let mut text = old_snapshot.text(); // let new_text = new_snapshot.text(); // for edit in edits.iter().rev() { // if !text.is_char_boundary(edit.old.start.value) // || !text.is_char_boundary(edit.old.end.value) // || !new_text.is_char_boundary(edit.new.start.value) // || !new_text.is_char_boundary(edit.new.end.value) // { // panic!( // "invalid edits: {:?}\nold text: {:?}\nnew text: {:?}", // edits, text, new_text // ); // } // text.replace_range( // edit.old.start.value..edit.old.end.value, // &new_text[edit.new.start.value..edit.new.end.value], // ); // } // pretty_assertions::assert_eq!(text, new_text, "invalid edits: {:?}", edits); // } // #[track_caller] // fn assert_chunks_in_ranges(snapshot: &DiffMapSnapshot) { // let full_text = snapshot.text(); // for ix in 0..full_text.len() { // let offset = DiffOffset(ix); // let mut chunks = // snapshot.chunks(DiffOffset(0)..snapshot.len(), false, Default::default()); // chunks.seek(offset..snapshot.len()); // let tail = chunks.map(|chunk| chunk.text).collect::(); // assert_eq!(tail, &full_text[ix..], "seek to range: {:?}", ix..); // let tail = snapshot // .chunks(offset..snapshot.len(), false, Default::default()) // .map(|chunk| chunk.text) // .collect::(); // assert_eq!(tail, &full_text[ix..], "start from range: {:?}", ix..); // let head = snapshot // .chunks(DiffOffset(0)..offset, false, Default::default()) // .map(|chunk| chunk.text) // .collect::(); // assert_eq!(head, &full_text[..ix], "start with range: {:?}", ..ix); // } // } // #[track_caller] // fn assert_consistent_line_numbers(snapshot: &DiffMapSnapshot) { // let all_line_numbers = snapshot.row_infos(0).collect::>(); // for start_row in 1..all_line_numbers.len() { // let line_numbers = snapshot.row_infos(start_row as u32).collect::>(); // assert_eq!( // line_numbers, // all_line_numbers[start_row..], // "start_row: {start_row}" // ); // for seek_row in 0..all_line_numbers.len() { // let mut numbers = snapshot.row_infos(start_row as u32); // numbers.seek(seek_row as u32); // let line_numbers = numbers.collect::>(); // assert_eq!( // line_numbers, // all_line_numbers[seek_row..], // "seek_row: {seek_row}, start_row: {start_row}" // ); // } // } // } // struct DiffMapDeps { // buffer: Model, // multibuffer: Model, // change_set: Model, // multibuffer_snapshot: MultiBufferSnapshot, // multibuffer_edits: text::Subscription, // } // fn build_diff_map( // text: &str, // base_text: Option<&str>, // cx: &mut TestAppContext, // ) -> (Model, DiffMapSnapshot, DiffMapDeps) { // let buffer = cx.new_model(|cx| Buffer::local(text, cx)); // let change_set = cx.new_model(|cx| { // let text_snapshot = buffer.read(cx).text_snapshot(); // let mut change_set = BufferChangeSet::new(&text_snapshot); // if let Some(base_text) = base_text { // let _ = change_set.set_base_text(base_text.to_string(), text_snapshot, cx); // } // change_set // }); // let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx)); // let (multibuffer_snapshot, multibuffer_edits) = // multibuffer.update(cx, |buffer, cx| (buffer.snapshot(cx), buffer.subscribe())); // let (diff_map, diff_map_snapshot) = cx.update(|cx| DiffMap::new(multibuffer.clone(), cx)); // diff_map.update(cx, |diff_map, cx| { // diff_map.add_change_set(change_set.clone(), cx) // }); // cx.run_until_parked(); // ( // diff_map, // diff_map_snapshot, // DiffMapDeps { // buffer, // multibuffer, // change_set, // multibuffer_snapshot, // multibuffer_edits, // }, // ) // } // impl DiffMapDeps { // fn update_buffer( // &mut self, // cx: &mut TestAppContext, // f: impl FnOnce(&mut Buffer, &mut ModelContext), // ) -> Vec> { // self.buffer.update(cx, f); // self.multibuffer_snapshot = self // .multibuffer // .read_with(cx, |buffer, cx| buffer.snapshot(cx)); // self.multibuffer_edits.consume().into_inner() // } // } // fn init_test(cx: &mut AppContext) { // let settings = SettingsStore::test(cx); // cx.set_global(settings); // language::init(cx); // crate::init(cx); // Project::init_settings(cx); // } // }