use super::{custom_highlights::CustomHighlightsChunks, DisplayAnchor};use crate::RowInfo;use collections::HashMap;use git::diff::DiffHunkStatus;use gpui::{AppContext, Context as _, HighlightStyle, Model, ModelContext, Subscription};use language::{BufferChunks, BufferId, Chunk};use multi_buffer::{ Anchor, AnchorRangeExt, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, ToOffset, ToPoint as _,};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 _};use util::debug_panic; pub(crate) struct DiffMap { snapshot: DiffMapSnapshot, multibuffer: Model<MultiBuffer>, 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>, pub(crate) transforms: SumTree<DiffTransform>, pub(crate) version: usize, pub(crate) buffer: MultiBufferSnapshot,} #[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 { multibuffer_map: TextSummary, diff_map: TextSummary,} impl DiffTransformSummary { pub fn multibuffer_point(&self) -> Point { self.multibuffer_map.lines } pub fn multibuffer_offset(&self) -> usize { self.multibuffer_map.len } pub fn diff_point(&self) -> DiffPoint { DiffPoint(self.diff_map.lines) } pub fn diff_offset(&self) -> DiffOffset { DiffOffset(self.diff_map.len) }} pub struct DiffMapChunks<'a> { snapshot: &'a DiffMapSnapshot, language_aware: bool, cursor: Cursor<'a, DiffTransform, (DiffOffset, usize)>, multibuffer_chunks: CustomHighlightsChunks<'a>, multibuffer_chunk: Option<Chunk<'a>>, multibuffer_offset: usize, offset: DiffOffset, end_offset: DiffOffset, diff_base_chunks: Option<(BufferId, BufferChunks<'a>)>,} #[derive(Clone)]pub struct DiffMapRows<'a> { cursor: Cursor<'a, DiffTransform, (DiffPoint, Point)>, diff_point: DiffPoint, input_buffer_rows: MultiBufferRows<'a>,} pub type DiffEdit = text::Edit<DiffOffset>; #[derive(Debug)]enum ChangeKind { DiffUpdated { base_changed: bool }, InputEdited, ExpandOrCollapseHunks { range: Range<usize>, expand: bool },} #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]pub struct DiffOffset(pub usize); #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]pub struct DiffPoint(pub Point); impl DiffPoint { pub fn new(row: u32, col: u32) -> Self { DiffPoint(Point::new(row, col)) } pub fn row(&self) -> u32 { self.0.row } pub fn column(&self) -> u32 { self.0.column }} impl DiffMap { pub fn new( multibuffer: Model<MultiBuffer>, cx: &mut AppContext, ) -> (Model<Self>, DiffMapSnapshot) { let multibuffer_snapshot = multibuffer.read(cx).snapshot(cx); let snapshot = DiffMapSnapshot { diffs: TreeMap::default(), version: 0, transforms: SumTree::from_item( DiffTransform::BufferContent { summary: multibuffer_snapshot.text_summary(), is_inserted_hunk: false, }, &(), ), buffer: multibuffer_snapshot, }; let this = cx.new_model(|_| Self { multibuffer, snapshot: snapshot.clone(), all_hunks_expanded: false, diff_bases: HashMap::default(), edits_since_sync: Patch::default(), }); (this, snapshot) } pub fn add_change_set( &mut self, change_set: Model<BufferChangeSet>, cx: &mut ModelContext<Self>, ) { 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, Self::buffer_diff_changed), 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, multibuffer_snapshot: MultiBufferSnapshot, buffer_edits: Vec<text::Edit<usize>>, cx: &mut ModelContext<Self>, ) -> (DiffMapSnapshot, Vec<DiffEdit>) { let changes = buffer_edits .iter() .map(|edit| (edit.clone(), ChangeKind::InputEdited)) .collect::<Vec<_>>(); self.snapshot.buffer = multibuffer_snapshot.clone(); self.recompute_transforms(changes, cx); ( self.snapshot.clone(), mem::take(&mut self.edits_since_sync).into_inner(), ) } fn buffer_diff_changed( &mut self, change_set: Model<BufferChangeSet>, cx: &mut ModelContext<Self>, ) { 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 = self.multibuffer.read(cx); let multibuffer_snapshot = self.snapshot.buffer(); let changes = multibuffer .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(Anchor::min()..Anchor::max()) .nth(1) .is_some() } pub(super) fn has_expanded_diff_hunks_in_ranges( &self, ranges: &[Range<multi_buffer::Anchor>], ) -> bool { let mut cursor = self.snapshot.transforms.cursor::<usize>(&()); let multibuffer_snapshot = self.snapshot.buffer(); for range in ranges { let range = range.to_point(multibuffer_snapshot); let start = multibuffer_snapshot.point_to_offset(Point::new(range.start.row, 0)); let end = multibuffer_snapshot.point_to_offset(Point::new(range.end.row + 1, 0)); let start = start.saturating_sub(1); let end = multibuffer_snapshot.len().min(end + 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<multi_buffer::Anchor>>, cx: &mut ModelContext<Self>, ) { self.expand_or_collapse_diff_hunks(ranges, true, cx); } pub(super) fn collapse_diff_hunks( &mut self, ranges: Vec<Range<multi_buffer::Anchor>>, cx: &mut ModelContext<Self>, ) { self.expand_or_collapse_diff_hunks(ranges, false, cx); } pub(super) fn set_all_hunks_expanded(&mut self, cx: &mut ModelContext<Self>) { 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<multi_buffer::Anchor>>, expand: bool, cx: &mut ModelContext<Self>, ) { let multibuffer_snapshot = self.snapshot.buffer(); let mut changes = Vec::new(); for range in ranges.iter() { let multibuffer_start = range.start.to_point(multibuffer_snapshot); let multibuffer_end = range.end.to_point(multibuffer_snapshot); let multibuffer_start = multibuffer_snapshot.point_to_offset(Point::new(multibuffer_start.row, 0)); let multibuffer_end = multibuffer_snapshot.point_to_offset(Point::new(multibuffer_end.row + 1, 0)); let expanded_start = multibuffer_start.saturating_sub(1); let expanded_end = multibuffer_snapshot.len().min(multibuffer_end); changes.push(( text::Edit { old: expanded_start..expanded_end, new: expanded_start..expanded_end, }, ChangeKind::ExpandOrCollapseHunks { range: multibuffer_start..multibuffer_end, expand, }, )); } self.recompute_transforms(changes, cx); } fn recompute_transforms( &mut self, changes: Vec<(text::Edit<usize>, ChangeKind)>, cx: &mut ModelContext<DiffMap>, ) { let multibuffer = self.multibuffer.read(cx); let multibuffer_snapshot = self.snapshot.buffer(); let mut cursor = self.snapshot.transforms.cursor::<(usize, 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 = 0; loop { let multibuffer_range = edit.new.clone(); let edit_old_start = cursor.start().1 + DiffOffset(edit.old.start - cursor.start().0); let mut edit_old_end = cursor.start().1 + DiffOffset(edit.old.end - cursor.start().0); for (buffer, buffer_range, excerpt_id) in multibuffer.range_to_buffer_ranges(multibuffer_range.clone(), cx) { let excerpt_range = multibuffer_snapshot .range_for_excerpt::<usize>(excerpt_id) .unwrap(); let excerpt_buffer_range = multibuffer_snapshot .buffer_range_for_excerpt(excerpt_id) .unwrap(); let buffer_id = buffer.read(cx).remote_id(); let diff_state = self.snapshot.diffs.get(&buffer_id); let buffer = buffer.read(cx); 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 + hunk_start_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((old_range.start.0 as isize + delta) as usize); delta -= (old_range.end - old_range.start).0 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_buffer_offset < range.end && hunk_end_buffer_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_byte_range = DiffOffset(hunk.diff_base_byte_range.len()); if base_text_summary.last_line_chars > 0 { diff_byte_range.0 += 1; base_text_summary.add_newline(); needs_newline = true; } Lines 1–500 of 2562. Load more
use super::{custom_highlights::CustomHighlightsChunks, DisplayAnchor}; use crate::RowInfo; use collections::HashMap; use git::diff::DiffHunkStatus; use gpui::{AppContext, Context as _, HighlightStyle, Model, ModelContext, Subscription}; use language::{BufferChunks, BufferId, Chunk}; use multi_buffer::{ Anchor, AnchorRangeExt, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, ToOffset, ToPoint as _, }; 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 _}; use util::debug_panic; pub(crate) struct DiffMap { snapshot: DiffMapSnapshot, multibuffer: Model<MultiBuffer>, 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>, pub(crate) transforms: SumTree<DiffTransform>, pub(crate) version: usize, pub(crate) buffer: MultiBufferSnapshot, } #[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 { multibuffer_map: TextSummary, diff_map: TextSummary, } impl DiffTransformSummary { pub fn multibuffer_point(&self) -> Point { self.multibuffer_map.lines } pub fn multibuffer_offset(&self) -> usize { self.multibuffer_map.len } pub fn diff_point(&self) -> DiffPoint { DiffPoint(self.diff_map.lines) } pub fn diff_offset(&self) -> DiffOffset { DiffOffset(self.diff_map.len) } } pub struct DiffMapChunks<'a> { snapshot: &'a DiffMapSnapshot, language_aware: bool, cursor: Cursor<'a, DiffTransform, (DiffOffset, usize)>, multibuffer_chunks: CustomHighlightsChunks<'a>, multibuffer_chunk: Option<Chunk<'a>>, multibuffer_offset: usize, offset: DiffOffset, end_offset: DiffOffset, diff_base_chunks: Option<(BufferId, BufferChunks<'a>)>, } #[derive(Clone)] pub struct DiffMapRows<'a> { cursor: Cursor<'a, DiffTransform, (DiffPoint, Point)>, diff_point: DiffPoint, input_buffer_rows: MultiBufferRows<'a>, } pub type DiffEdit = text::Edit<DiffOffset>; #[derive(Debug)] enum ChangeKind { DiffUpdated { base_changed: bool }, InputEdited, ExpandOrCollapseHunks { range: Range<usize>, expand: bool }, } #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct DiffOffset(pub usize); #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct DiffPoint(pub Point); impl DiffPoint { pub fn new(row: u32, col: u32) -> Self { DiffPoint(Point::new(row, col)) } pub fn row(&self) -> u32 { self.0.row } pub fn column(&self) -> u32 { self.0.column } } impl DiffMap { pub fn new( multibuffer: Model<MultiBuffer>, cx: &mut AppContext, ) -> (Model<Self>, DiffMapSnapshot) { let multibuffer_snapshot = multibuffer.read(cx).snapshot(cx); let snapshot = DiffMapSnapshot { diffs: TreeMap::default(), version: 0, transforms: SumTree::from_item( DiffTransform::BufferContent { summary: multibuffer_snapshot.text_summary(), is_inserted_hunk: false, }, &(), ), buffer: multibuffer_snapshot, }; let this = cx.new_model(|_| Self { multibuffer, snapshot: snapshot.clone(), all_hunks_expanded: false, diff_bases: HashMap::default(), edits_since_sync: Patch::default(), }); (this, snapshot) } pub fn add_change_set( &mut self, change_set: Model<BufferChangeSet>, cx: &mut ModelContext<Self>, ) { 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, Self::buffer_diff_changed), 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, multibuffer_snapshot: MultiBufferSnapshot, buffer_edits: Vec<text::Edit<usize>>, cx: &mut ModelContext<Self>, ) -> (DiffMapSnapshot, Vec<DiffEdit>) { let changes = buffer_edits .iter() .map(|edit| (edit.clone(), ChangeKind::InputEdited)) .collect::<Vec<_>>(); self.snapshot.buffer = multibuffer_snapshot.clone(); self.recompute_transforms(changes, cx); ( self.snapshot.clone(), mem::take(&mut self.edits_since_sync).into_inner(), ) } fn buffer_diff_changed( &mut self, change_set: Model<BufferChangeSet>, cx: &mut ModelContext<Self>, ) { 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 = self.multibuffer.read(cx); let multibuffer_snapshot = self.snapshot.buffer(); let changes = multibuffer .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(Anchor::min()..Anchor::max()) .nth(1) .is_some() } pub(super) fn has_expanded_diff_hunks_in_ranges( &self, ranges: &[Range<multi_buffer::Anchor>], ) -> bool { let mut cursor = self.snapshot.transforms.cursor::<usize>(&()); let multibuffer_snapshot = self.snapshot.buffer(); for range in ranges { let range = range.to_point(multibuffer_snapshot); let start = multibuffer_snapshot.point_to_offset(Point::new(range.start.row, 0)); let end = multibuffer_snapshot.point_to_offset(Point::new(range.end.row + 1, 0)); let start = start.saturating_sub(1); let end = multibuffer_snapshot.len().min(end + 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<multi_buffer::Anchor>>, cx: &mut ModelContext<Self>, ) { self.expand_or_collapse_diff_hunks(ranges, true, cx); } pub(super) fn collapse_diff_hunks( &mut self, ranges: Vec<Range<multi_buffer::Anchor>>, cx: &mut ModelContext<Self>, ) { self.expand_or_collapse_diff_hunks(ranges, false, cx); } pub(super) fn set_all_hunks_expanded(&mut self, cx: &mut ModelContext<Self>) { 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<multi_buffer::Anchor>>, expand: bool, cx: &mut ModelContext<Self>, ) { let multibuffer_snapshot = self.snapshot.buffer(); let mut changes = Vec::new(); for range in ranges.iter() { let multibuffer_start = range.start.to_point(multibuffer_snapshot); let multibuffer_end = range.end.to_point(multibuffer_snapshot); let multibuffer_start = multibuffer_snapshot.point_to_offset(Point::new(multibuffer_start.row, 0)); let multibuffer_end = multibuffer_snapshot.point_to_offset(Point::new(multibuffer_end.row + 1, 0)); let expanded_start = multibuffer_start.saturating_sub(1); let expanded_end = multibuffer_snapshot.len().min(multibuffer_end); changes.push(( text::Edit { old: expanded_start..expanded_end, new: expanded_start..expanded_end, }, ChangeKind::ExpandOrCollapseHunks { range: multibuffer_start..multibuffer_end, expand, }, )); } self.recompute_transforms(changes, cx); } fn recompute_transforms( &mut self, changes: Vec<(text::Edit<usize>, ChangeKind)>, cx: &mut ModelContext<DiffMap>, ) { let multibuffer = self.multibuffer.read(cx); let multibuffer_snapshot = self.snapshot.buffer(); let mut cursor = self.snapshot.transforms.cursor::<(usize, 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 = 0; loop { let multibuffer_range = edit.new.clone(); let edit_old_start = cursor.start().1 + DiffOffset(edit.old.start - cursor.start().0); let mut edit_old_end = cursor.start().1 + DiffOffset(edit.old.end - cursor.start().0); for (buffer, buffer_range, excerpt_id) in multibuffer.range_to_buffer_ranges(multibuffer_range.clone(), cx) { let excerpt_range = multibuffer_snapshot .range_for_excerpt::<usize>(excerpt_id) .unwrap(); let excerpt_buffer_range = multibuffer_snapshot .buffer_range_for_excerpt(excerpt_id) .unwrap(); let buffer_id = buffer.read(cx).remote_id(); let diff_state = self.snapshot.diffs.get(&buffer_id); let buffer = buffer.read(cx); 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 + hunk_start_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((old_range.start.0 as isize + delta) as usize); delta -= (old_range.end - old_range.start).0 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_buffer_offset < range.end && hunk_end_buffer_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_byte_range = DiffOffset(hunk.diff_base_byte_range.len()); if base_text_summary.last_line_chars > 0 { diff_byte_range.0 += 1; base_text_summary.add_newline(); needs_newline = true; }