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<BufferId, ChangeSetState>, all_hunks_expanded: bool, edits_since_sync: Patch<DiffOffset>,} struct ChangeSetState { change_set: Model<BufferChangeSet>, _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<BufferId, DiffSnapshot>, excerpt_map_snapshot: ExcerptMapSnapshot, pub(crate) transforms: SumTree<DiffTransform>, 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<usize>, 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<Chunk<'a>>, 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<DiffOffset>; #[derive(Debug)]enum ChangeKind { DiffUpdated { base_changed: bool, }, InputEdited, ExpandOrCollapseHunks { range: Range<ExcerptOffset>, expand: bool, },} pub type DiffOffset = TypedOffset<DiffMap>;pub type DiffPoint = TypedPoint<DiffMap>;pub type DiffPointUtf16 = TypedPointUtf16<DiffMap>;pub type DiffRow = TypedRow<DiffMap>; 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<BufferChangeSet>, cx: &mut ModelContext<MultiBuffer>, ) { 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<BufferChangeSet>> { self.diff_bases .get(&buffer_id) .map(|state| &state.change_set) } pub fn sync( &mut self, input_edits: Vec<text::Edit<ExcerptOffset>>, cx: &AppContext, ) -> (DiffMapSnapshot, Vec<DiffEdit>) { let changes = input_edits .iter() .map(|edit| (edit.clone(), ChangeKind::InputEdited)) .collect::<Vec<_>>(); 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<BufferChangeSet>, 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<DiffOffset>]) -> bool { let mut cursor = self.snapshot.transforms.cursor::<ExcerptOffset>(&()); 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<Range<Anchor>>, cx: &mut AppContext) { todo!(); // self.expand_or_collapse_diff_hunks(ranges, true, cx); } pub(super) fn collapse_diff_hunks(&mut self, ranges: Vec<Range<Anchor>>, 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<Range<DiffOffset>>, 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<ExcerptOffset>, 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::<ExcerptOffset>(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::<Point>(hunk.diff_base_byte_range.start); let mut base_text_summary = text_cursor .summary::<TextSummary>(hunk.diff_base_byte_range.end); let mut needs_newline = false; let mut diff_len =Lines 1–500 of 2529. Load more
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<BufferId, ChangeSetState>, all_hunks_expanded: bool, edits_since_sync: Patch<DiffOffset>, } struct ChangeSetState { change_set: Model<BufferChangeSet>, _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<BufferId, DiffSnapshot>, excerpt_map_snapshot: ExcerptMapSnapshot, pub(crate) transforms: SumTree<DiffTransform>, 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<usize>, 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<Chunk<'a>>, 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<DiffOffset>; #[derive(Debug)] enum ChangeKind { DiffUpdated { base_changed: bool, }, InputEdited, ExpandOrCollapseHunks { range: Range<ExcerptOffset>, expand: bool, }, } pub type DiffOffset = TypedOffset<DiffMap>; pub type DiffPoint = TypedPoint<DiffMap>; pub type DiffPointUtf16 = TypedPointUtf16<DiffMap>; pub type DiffRow = TypedRow<DiffMap>; 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<BufferChangeSet>, cx: &mut ModelContext<MultiBuffer>, ) { 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<BufferChangeSet>> { self.diff_bases .get(&buffer_id) .map(|state| &state.change_set) } pub fn sync( &mut self, input_edits: Vec<text::Edit<ExcerptOffset>>, cx: &AppContext, ) -> (DiffMapSnapshot, Vec<DiffEdit>) { let changes = input_edits .iter() .map(|edit| (edit.clone(), ChangeKind::InputEdited)) .collect::<Vec<_>>(); 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<BufferChangeSet>, 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<DiffOffset>]) -> bool { let mut cursor = self.snapshot.transforms.cursor::<ExcerptOffset>(&()); 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<Range<Anchor>>, cx: &mut AppContext) { todo!(); // self.expand_or_collapse_diff_hunks(ranges, true, cx); } pub(super) fn collapse_diff_hunks(&mut self, ranges: Vec<Range<Anchor>>, 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<Range<DiffOffset>>, 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<ExcerptOffset>, 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::<ExcerptOffset>(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::<Point>(hunk.diff_base_byte_range.start); let mut base_text_summary = text_cursor .summary::<TextSummary>(hunk.diff_base_byte_range.end); let mut needs_newline = false; let mut diff_len =