use futures::channel::oneshot;use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task};use language::{ Capability, Diff, DiffOptions, File, Language, LanguageName, LanguageRegistry, language_settings::language_settings, word_diff_ranges,};use rope::Rope;use std::{ cmp::Ordering, future::Future, iter, ops::{Range, RangeInclusive}, sync::Arc,};use sum_tree::SumTree;use text::{ Anchor, Bias, BufferId, Edit, OffsetRangeExt, Patch, Point, ToOffset as _, ToPoint as _,};use util::ResultExt; pub const MAX_WORD_DIFF_LINE_COUNT: usize = 5; pub struct BufferDiff { pub buffer_id: BufferId, inner: BufferDiffInner<Entity<language::Buffer>>, secondary_diff: Option<Entity<BufferDiff>>,} #[derive(Clone)]pub struct BufferDiffSnapshot { inner: BufferDiffInner<language::BufferSnapshot>, secondary_diff: Option<Arc<BufferDiffSnapshot>>,} impl std::fmt::Debug for BufferDiffSnapshot { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("BufferDiffSnapshot") .field("inner", &self.inner) .field("secondary_diff", &self.secondary_diff) .finish() }} #[derive(Clone)]pub struct BufferDiffUpdate { inner: BufferDiffInner<Arc<str>>, buffer_snapshot: text::BufferSnapshot, base_text_edits: Option<Diff>, base_text_changed: bool,} #[derive(Clone)]struct BufferDiffInner<BaseText> { hunks: SumTree<InternalDiffHunk>, pending_hunks: SumTree<PendingHunk>, base_text: BaseText, base_text_exists: bool, buffer_snapshot: text::BufferSnapshot,} impl<BaseText> BufferDiffInner<BaseText> { fn buffer_version(&self) -> &clock::Global { self.buffer_snapshot.version() }} #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]pub struct DiffHunkStatus { pub kind: DiffHunkStatusKind, pub secondary: DiffHunkSecondaryStatus,} #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]pub enum DiffHunkStatusKind { Added, Modified, Deleted,} #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]/// Diff of Working Copy vs Index/// aka 'is this hunk staged or not'pub enum DiffHunkSecondaryStatus { /// Unstaged HasSecondaryHunk, /// Partially staged OverlapsWithSecondaryHunk, /// Staged NoSecondaryHunk, /// We are unstaging SecondaryHunkAdditionPending, /// We are stagind SecondaryHunkRemovalPending,} /// A diff hunk resolved to rows in the buffer.#[derive(Debug, Clone, PartialEq, Eq)]pub struct DiffHunk { /// The buffer range as points. pub range: Range<Point>, /// The range in the buffer to which this hunk corresponds. pub buffer_range: Range<Anchor>, /// The range in the buffer's diff base text to which this hunk corresponds. pub diff_base_byte_range: Range<usize>, pub secondary_status: DiffHunkSecondaryStatus, // Anchors representing the word diff locations in the active buffer pub buffer_word_diffs: Vec<Range<Anchor>>, // Offsets relative to the start of the deleted diff that represent word diff locations pub base_word_diffs: Vec<Range<usize>>,} /// We store [`InternalDiffHunk`]s internally so we don't need to store the additional row range.#[derive(Debug, Clone, PartialEq, Eq)]struct InternalDiffHunk { buffer_range: Range<Anchor>, diff_base_byte_range: Range<usize>, base_word_diffs: Vec<Range<usize>>, buffer_word_diffs: Vec<Range<Anchor>>,} #[derive(Debug, Clone, PartialEq, Eq)]struct PendingHunk { buffer_range: Range<Anchor>, diff_base_byte_range: Range<usize>, buffer_version: clock::Global, new_status: DiffHunkSecondaryStatus,} #[derive(Debug, Clone)]pub struct DiffHunkSummary { buffer_range: Range<Anchor>, diff_base_byte_range: Range<usize>,} impl sum_tree::Item for InternalDiffHunk { type Summary = DiffHunkSummary; fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary { DiffHunkSummary { buffer_range: self.buffer_range.clone(), diff_base_byte_range: self.diff_base_byte_range.clone(), } }} impl sum_tree::Item for PendingHunk { type Summary = DiffHunkSummary; fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary { DiffHunkSummary { buffer_range: self.buffer_range.clone(), diff_base_byte_range: self.diff_base_byte_range.clone(), } }} impl sum_tree::Summary for DiffHunkSummary { type Context<'a> = &'a text::BufferSnapshot; fn zero(_cx: Self::Context<'_>) -> Self { DiffHunkSummary { buffer_range: Anchor::MIN..Anchor::MIN, diff_base_byte_range: 0..0, } } fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) { self.buffer_range.start = *self .buffer_range .start .min(&other.buffer_range.start, buffer); self.buffer_range.end = *self.buffer_range.end.max(&other.buffer_range.end, buffer); self.diff_base_byte_range.start = self .diff_base_byte_range .start .min(other.diff_base_byte_range.start); self.diff_base_byte_range.end = self .diff_base_byte_range .end .max(other.diff_base_byte_range.end); }} impl sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for Anchor { fn cmp(&self, cursor_location: &DiffHunkSummary, buffer: &text::BufferSnapshot) -> Ordering { if self .cmp(&cursor_location.buffer_range.start, buffer) .is_lt() { Ordering::Less } else if self.cmp(&cursor_location.buffer_range.end, buffer).is_gt() { Ordering::Greater } else { Ordering::Equal } }} impl sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for usize { fn cmp(&self, cursor_location: &DiffHunkSummary, _cx: &text::BufferSnapshot) -> Ordering { if *self < cursor_location.diff_base_byte_range.start { Ordering::Less } else if *self > cursor_location.diff_base_byte_range.end { Ordering::Greater } else { Ordering::Equal } }} impl std::fmt::Debug for BufferDiffInner<language::BufferSnapshot> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("BufferDiffSnapshot") .field("hunks", &self.hunks) .field("remote_id", &self.base_text.remote_id()) .finish() }} impl BufferDiffSnapshot { #[cfg(test)] fn new_sync( buffer: &text::BufferSnapshot, diff_base: String, cx: &mut gpui::TestAppContext, ) -> BufferDiffSnapshot { let buffer_diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, buffer, cx)); buffer_diff.update(cx, |buffer_diff, cx| buffer_diff.snapshot(cx)) } pub fn is_empty(&self) -> bool { self.inner.hunks.is_empty() } pub fn base_text_string(&self) -> Option<String> { self.inner .base_text_exists .then(|| self.inner.base_text.text()) } pub fn secondary_diff(&self) -> Option<&BufferDiffSnapshot> { self.secondary_diff.as_deref() } pub fn buffer_version(&self) -> &clock::Global { self.inner.buffer_version() } fn original_buffer_snapshot(&self) -> &text::BufferSnapshot { &self.inner.buffer_snapshot } #[ztracing::instrument(skip_all)] pub fn hunks_intersecting_range<'a>( &'a self, range: Range<Anchor>, buffer: &'a text::BufferSnapshot, ) -> impl 'a + Iterator<Item = DiffHunk> { let unstaged_counterpart = self.secondary_diff.as_ref().map(|diff| &diff.inner); self.inner .hunks_intersecting_range(range, buffer, unstaged_counterpart) } pub fn hunks_intersecting_range_rev<'a>( &'a self, range: Range<Anchor>, buffer: &'a text::BufferSnapshot, ) -> impl 'a + Iterator<Item = DiffHunk> { let filter = move |summary: &DiffHunkSummary| { let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt(); let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt(); !before_start && !after_end }; self.inner.hunks_intersecting_range_rev_impl(filter, buffer) } pub fn hunks_intersecting_base_text_range<'a>( &'a self, range: Range<usize>, main_buffer: &'a text::BufferSnapshot, ) -> impl 'a + Iterator<Item = DiffHunk> { let unstaged_counterpart = self.secondary_diff.as_ref().map(|diff| &diff.inner); let filter = move |summary: &DiffHunkSummary| { let before_start = summary.diff_base_byte_range.end < range.start; let after_end = summary.diff_base_byte_range.start > range.end; !before_start && !after_end }; self.inner .hunks_intersecting_range_impl(filter, main_buffer, unstaged_counterpart) } pub fn hunks_intersecting_base_text_range_rev<'a>( &'a self, range: Range<usize>, main_buffer: &'a text::BufferSnapshot, ) -> impl 'a + Iterator<Item = DiffHunk> { let filter = move |summary: &DiffHunkSummary| { let before_start = summary.diff_base_byte_range.end.cmp(&range.start).is_lt(); let after_end = summary.diff_base_byte_range.start.cmp(&range.end).is_gt(); !before_start && !after_end }; self.inner .hunks_intersecting_range_rev_impl(filter, main_buffer) } pub fn hunks<'a>( &'a self, buffer_snapshot: &'a text::BufferSnapshot, ) -> impl 'a + Iterator<Item = DiffHunk> { self.hunks_intersecting_range( Anchor::min_max_range_for_buffer(buffer_snapshot.remote_id()), buffer_snapshot, ) } pub fn hunks_in_row_range<'a>( &'a self, range: Range<u32>, buffer: &'a text::BufferSnapshot, ) -> impl 'a + Iterator<Item = DiffHunk> { let start = buffer.anchor_before(Point::new(range.start, 0)); let end = buffer.anchor_after(Point::new(range.end, 0)); self.hunks_intersecting_range(start..end, buffer) } pub fn range_to_hunk_range( &self, range: Range<Anchor>, buffer: &text::BufferSnapshot, ) -> (Option<Range<Anchor>>, Option<Range<usize>>) { let first_hunk = self.hunks_intersecting_range(range.clone(), buffer).next(); let last_hunk = self.hunks_intersecting_range_rev(range, buffer).next(); let range = first_hunk .as_ref() .zip(last_hunk.as_ref()) .map(|(first, last)| first.buffer_range.start..last.buffer_range.end); let base_text_range = first_hunk .zip(last_hunk) .map(|(first, last)| first.diff_base_byte_range.start..last.diff_base_byte_range.end); (range, base_text_range) } pub fn base_text(&self) -> &language::BufferSnapshot { &self.inner.base_text } /// If this function returns `true`, the base texts are equal. If this /// function returns `false`, they might be equal, but might not. This /// result is used to avoid recalculating diffs in situations where we know /// nothing has changed. pub fn base_texts_definitely_eq(&self, other: &Self) -> bool { if self.inner.base_text_exists != other.inner.base_text_exists { return false; } let left = &self.inner.base_text; let right = &other.inner.base_text; let (old_id, old_version, old_empty) = (left.remote_id(), left.version(), left.is_empty()); let (new_id, new_version, new_empty) = (right.remote_id(), right.version(), right.is_empty()); (new_id == old_id && new_version == old_version) || (new_empty && old_empty) } /// Returns the last hunk whose start is less than or equal to the given position. fn hunk_before_base_text_offset<'a>( &self, target: usize, cursor: &mut sum_tree::Cursor<'a, '_, InternalDiffHunk, DiffHunkSummary>, ) -> Option<&'a InternalDiffHunk> { cursor.seek_forward(&target, Bias::Left); if cursor .item() .is_none_or(|hunk| target < hunk.diff_base_byte_range.start) { cursor.prev(); } cursor .item() .filter(|hunk| target >= hunk.diff_base_byte_range.start) } fn hunk_before_buffer_anchor<'a>( &self, target: Anchor, cursor: &mut sum_tree::Cursor<'a, '_, InternalDiffHunk, DiffHunkSummary>, buffer: &text::BufferSnapshot, ) -> Option<&'a InternalDiffHunk> { cursor.seek_forward(&target, Bias::Left); if cursor .item() .is_none_or(|hunk| target.cmp(&hunk.buffer_range.start, buffer).is_lt()) { cursor.prev(); } cursor .item() .filter(|hunk| target.cmp(&hunk.buffer_range.start, buffer).is_ge()) } /// Returns a patch mapping the provided main buffer snapshot to the base text of this diff. /// /// The returned patch is guaranteed to be accurate for all main buffer points in the provided range, /// but not necessarily for points outside that range. pub fn patch_for_buffer_range<'a>( &'a self, range: RangeInclusive<Point>, buffer: &'a text::BufferSnapshot, ) -> Patch<Point> { if !self.inner.base_text_exists { return Patch::new(vec![Edit { old: Point::zero()..buffer.max_point(), new: Point::zero()..Point::zero(), }]); } let mut edits_since_diff = Patch::new( buffer .edits_since::<Point>(&self.inner.buffer_snapshot.version) .collect::<Vec<_>>(), ); edits_since_diff.invert(); let mut start_point = edits_since_diff.old_to_new(*range.start()); if let Some(first_edit) = edits_since_diff.edits().first() { start_point = start_point.min(first_edit.new.start); } let original_snapshot = self.original_buffer_snapshot(); let base_text = self.base_text(); let mut cursor = self.inner.hunks.cursor(original_snapshot); self.hunk_before_buffer_anchor( original_snapshot.anchor_before(start_point), &mut cursor, original_snapshot, ); if cursor.item().is_none() { cursor.next(); } let mut prefix_edit = cursor.prev_item().map(|prev_hunk| Edit { old: Point::zero()..prev_hunk.buffer_range.end.to_point(original_snapshot), new: Point::zero()..prev_hunk.diff_base_byte_range.end.to_point(base_text), }); let mut range_end = edits_since_diff.old_to_new(*range.end()); if let Some(last_edit) = edits_since_diff.edits().last() { range_end = range_end.max(last_edit.new.end); } let range_end = original_snapshot.anchor_before(range_end); let hunk_iter = std::iter::from_fn(move || { if let Some(edit) = prefix_edit.take() { return Some(edit); } let hunk = cursor.item()?; if hunk .buffer_range .start .cmp(&range_end, original_snapshot) .is_gt() { return None; } let edit = Edit { old: hunk.buffer_range.to_point(original_snapshot), new: hunk.diff_base_byte_range.to_point(base_text), }; cursor.next(); Some(edit) }); edits_since_diff.compose(hunk_iter) } #[cfg(test)] pub(crate) fn patch_for_buffer_range_naive<'a>( &'a self, buffer: &'a text::BufferSnapshot, ) -> Patch<Point> { let original_snapshot = self.original_buffer_snapshot(); let edits_since: Vec<Edit<Point>> = buffer .edits_since::<Point>(original_snapshot.version()) .collect(); let mut inverted_edits_since = Patch::new(edits_since); inverted_edits_since.invert(); inverted_edits_since.compose( self.inner .hunks .iter() .map(|hunk| { let old_start = hunk.buffer_range.start.to_point(original_snapshot); let old_end = hunk.buffer_range.end.to_point(original_snapshot); let new_start = self .base_text() .offset_to_point(hunk.diff_base_byte_range.start); let new_end = selfLines 1–500 of 3952. Load more
use futures::channel::oneshot; use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch}; use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task}; use language::{ Capability, Diff, DiffOptions, File, Language, LanguageName, LanguageRegistry, language_settings::language_settings, word_diff_ranges, }; use rope::Rope; use std::{ cmp::Ordering, future::Future, iter, ops::{Range, RangeInclusive}, sync::Arc, }; use sum_tree::SumTree; use text::{ Anchor, Bias, BufferId, Edit, OffsetRangeExt, Patch, Point, ToOffset as _, ToPoint as _, }; use util::ResultExt; pub const MAX_WORD_DIFF_LINE_COUNT: usize = 5; pub struct BufferDiff { pub buffer_id: BufferId, inner: BufferDiffInner<Entity<language::Buffer>>, secondary_diff: Option<Entity<BufferDiff>>, } #[derive(Clone)] pub struct BufferDiffSnapshot { inner: BufferDiffInner<language::BufferSnapshot>, secondary_diff: Option<Arc<BufferDiffSnapshot>>, } impl std::fmt::Debug for BufferDiffSnapshot { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("BufferDiffSnapshot") .field("inner", &self.inner) .field("secondary_diff", &self.secondary_diff) .finish() } } #[derive(Clone)] pub struct BufferDiffUpdate { inner: BufferDiffInner<Arc<str>>, buffer_snapshot: text::BufferSnapshot, base_text_edits: Option<Diff>, base_text_changed: bool, } #[derive(Clone)] struct BufferDiffInner<BaseText> { hunks: SumTree<InternalDiffHunk>, pending_hunks: SumTree<PendingHunk>, base_text: BaseText, base_text_exists: bool, buffer_snapshot: text::BufferSnapshot, } impl<BaseText> BufferDiffInner<BaseText> { fn buffer_version(&self) -> &clock::Global { self.buffer_snapshot.version() } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct DiffHunkStatus { pub kind: DiffHunkStatusKind, pub secondary: DiffHunkSecondaryStatus, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum DiffHunkStatusKind { Added, Modified, Deleted, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] /// Diff of Working Copy vs Index /// aka 'is this hunk staged or not' pub enum DiffHunkSecondaryStatus { /// Unstaged HasSecondaryHunk, /// Partially staged OverlapsWithSecondaryHunk, /// Staged NoSecondaryHunk, /// We are unstaging SecondaryHunkAdditionPending, /// We are stagind SecondaryHunkRemovalPending, } /// A diff hunk resolved to rows in the buffer. #[derive(Debug, Clone, PartialEq, Eq)] pub struct DiffHunk { /// The buffer range as points. pub range: Range<Point>, /// The range in the buffer to which this hunk corresponds. pub buffer_range: Range<Anchor>, /// The range in the buffer's diff base text to which this hunk corresponds. pub diff_base_byte_range: Range<usize>, pub secondary_status: DiffHunkSecondaryStatus, // Anchors representing the word diff locations in the active buffer pub buffer_word_diffs: Vec<Range<Anchor>>, // Offsets relative to the start of the deleted diff that represent word diff locations pub base_word_diffs: Vec<Range<usize>>, } /// We store [`InternalDiffHunk`]s internally so we don't need to store the additional row range. #[derive(Debug, Clone, PartialEq, Eq)] struct InternalDiffHunk { buffer_range: Range<Anchor>, diff_base_byte_range: Range<usize>, base_word_diffs: Vec<Range<usize>>, buffer_word_diffs: Vec<Range<Anchor>>, } #[derive(Debug, Clone, PartialEq, Eq)] struct PendingHunk { buffer_range: Range<Anchor>, diff_base_byte_range: Range<usize>, buffer_version: clock::Global, new_status: DiffHunkSecondaryStatus, } #[derive(Debug, Clone)] pub struct DiffHunkSummary { buffer_range: Range<Anchor>, diff_base_byte_range: Range<usize>, } impl sum_tree::Item for InternalDiffHunk { type Summary = DiffHunkSummary; fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary { DiffHunkSummary { buffer_range: self.buffer_range.clone(), diff_base_byte_range: self.diff_base_byte_range.clone(), } } } impl sum_tree::Item for PendingHunk { type Summary = DiffHunkSummary; fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary { DiffHunkSummary { buffer_range: self.buffer_range.clone(), diff_base_byte_range: self.diff_base_byte_range.clone(), } } } impl sum_tree::Summary for DiffHunkSummary { type Context<'a> = &'a text::BufferSnapshot; fn zero(_cx: Self::Context<'_>) -> Self { DiffHunkSummary { buffer_range: Anchor::MIN..Anchor::MIN, diff_base_byte_range: 0..0, } } fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) { self.buffer_range.start = *self .buffer_range .start .min(&other.buffer_range.start, buffer); self.buffer_range.end = *self.buffer_range.end.max(&other.buffer_range.end, buffer); self.diff_base_byte_range.start = self .diff_base_byte_range .start .min(other.diff_base_byte_range.start); self.diff_base_byte_range.end = self .diff_base_byte_range .end .max(other.diff_base_byte_range.end); } } impl sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for Anchor { fn cmp(&self, cursor_location: &DiffHunkSummary, buffer: &text::BufferSnapshot) -> Ordering { if self .cmp(&cursor_location.buffer_range.start, buffer) .is_lt() { Ordering::Less } else if self.cmp(&cursor_location.buffer_range.end, buffer).is_gt() { Ordering::Greater } else { Ordering::Equal } } } impl sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for usize { fn cmp(&self, cursor_location: &DiffHunkSummary, _cx: &text::BufferSnapshot) -> Ordering { if *self < cursor_location.diff_base_byte_range.start { Ordering::Less } else if *self > cursor_location.diff_base_byte_range.end { Ordering::Greater } else { Ordering::Equal } } } impl std::fmt::Debug for BufferDiffInner<language::BufferSnapshot> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("BufferDiffSnapshot") .field("hunks", &self.hunks) .field("remote_id", &self.base_text.remote_id()) .finish() } } impl BufferDiffSnapshot { #[cfg(test)] fn new_sync( buffer: &text::BufferSnapshot, diff_base: String, cx: &mut gpui::TestAppContext, ) -> BufferDiffSnapshot { let buffer_diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, buffer, cx)); buffer_diff.update(cx, |buffer_diff, cx| buffer_diff.snapshot(cx)) } pub fn is_empty(&self) -> bool { self.inner.hunks.is_empty() } pub fn base_text_string(&self) -> Option<String> { self.inner .base_text_exists .then(|| self.inner.base_text.text()) } pub fn secondary_diff(&self) -> Option<&BufferDiffSnapshot> { self.secondary_diff.as_deref() } pub fn buffer_version(&self) -> &clock::Global { self.inner.buffer_version() } fn original_buffer_snapshot(&self) -> &text::BufferSnapshot { &self.inner.buffer_snapshot } #[ztracing::instrument(skip_all)] pub fn hunks_intersecting_range<'a>( &'a self, range: Range<Anchor>, buffer: &'a text::BufferSnapshot, ) -> impl 'a + Iterator<Item = DiffHunk> { let unstaged_counterpart = self.secondary_diff.as_ref().map(|diff| &diff.inner); self.inner .hunks_intersecting_range(range, buffer, unstaged_counterpart) } pub fn hunks_intersecting_range_rev<'a>( &'a self, range: Range<Anchor>, buffer: &'a text::BufferSnapshot, ) -> impl 'a + Iterator<Item = DiffHunk> { let filter = move |summary: &DiffHunkSummary| { let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt(); let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt(); !before_start && !after_end }; self.inner.hunks_intersecting_range_rev_impl(filter, buffer) } pub fn hunks_intersecting_base_text_range<'a>( &'a self, range: Range<usize>, main_buffer: &'a text::BufferSnapshot, ) -> impl 'a + Iterator<Item = DiffHunk> { let unstaged_counterpart = self.secondary_diff.as_ref().map(|diff| &diff.inner); let filter = move |summary: &DiffHunkSummary| { let before_start = summary.diff_base_byte_range.end < range.start; let after_end = summary.diff_base_byte_range.start > range.end; !before_start && !after_end }; self.inner .hunks_intersecting_range_impl(filter, main_buffer, unstaged_counterpart) } pub fn hunks_intersecting_base_text_range_rev<'a>( &'a self, range: Range<usize>, main_buffer: &'a text::BufferSnapshot, ) -> impl 'a + Iterator<Item = DiffHunk> { let filter = move |summary: &DiffHunkSummary| { let before_start = summary.diff_base_byte_range.end.cmp(&range.start).is_lt(); let after_end = summary.diff_base_byte_range.start.cmp(&range.end).is_gt(); !before_start && !after_end }; self.inner .hunks_intersecting_range_rev_impl(filter, main_buffer) } pub fn hunks<'a>( &'a self, buffer_snapshot: &'a text::BufferSnapshot, ) -> impl 'a + Iterator<Item = DiffHunk> { self.hunks_intersecting_range( Anchor::min_max_range_for_buffer(buffer_snapshot.remote_id()), buffer_snapshot, ) } pub fn hunks_in_row_range<'a>( &'a self, range: Range<u32>, buffer: &'a text::BufferSnapshot, ) -> impl 'a + Iterator<Item = DiffHunk> { let start = buffer.anchor_before(Point::new(range.start, 0)); let end = buffer.anchor_after(Point::new(range.end, 0)); self.hunks_intersecting_range(start..end, buffer) } pub fn range_to_hunk_range( &self, range: Range<Anchor>, buffer: &text::BufferSnapshot, ) -> (Option<Range<Anchor>>, Option<Range<usize>>) { let first_hunk = self.hunks_intersecting_range(range.clone(), buffer).next(); let last_hunk = self.hunks_intersecting_range_rev(range, buffer).next(); let range = first_hunk .as_ref() .zip(last_hunk.as_ref()) .map(|(first, last)| first.buffer_range.start..last.buffer_range.end); let base_text_range = first_hunk .zip(last_hunk) .map(|(first, last)| first.diff_base_byte_range.start..last.diff_base_byte_range.end); (range, base_text_range) } pub fn base_text(&self) -> &language::BufferSnapshot { &self.inner.base_text } /// If this function returns `true`, the base texts are equal. If this /// function returns `false`, they might be equal, but might not. This /// result is used to avoid recalculating diffs in situations where we know /// nothing has changed. pub fn base_texts_definitely_eq(&self, other: &Self) -> bool { if self.inner.base_text_exists != other.inner.base_text_exists { return false; } let left = &self.inner.base_text; let right = &other.inner.base_text; let (old_id, old_version, old_empty) = (left.remote_id(), left.version(), left.is_empty()); let (new_id, new_version, new_empty) = (right.remote_id(), right.version(), right.is_empty()); (new_id == old_id && new_version == old_version) || (new_empty && old_empty) } /// Returns the last hunk whose start is less than or equal to the given position. fn hunk_before_base_text_offset<'a>( &self, target: usize, cursor: &mut sum_tree::Cursor<'a, '_, InternalDiffHunk, DiffHunkSummary>, ) -> Option<&'a InternalDiffHunk> { cursor.seek_forward(&target, Bias::Left); if cursor .item() .is_none_or(|hunk| target < hunk.diff_base_byte_range.start) { cursor.prev(); } cursor .item() .filter(|hunk| target >= hunk.diff_base_byte_range.start) } fn hunk_before_buffer_anchor<'a>( &self, target: Anchor, cursor: &mut sum_tree::Cursor<'a, '_, InternalDiffHunk, DiffHunkSummary>, buffer: &text::BufferSnapshot, ) -> Option<&'a InternalDiffHunk> { cursor.seek_forward(&target, Bias::Left); if cursor .item() .is_none_or(|hunk| target.cmp(&hunk.buffer_range.start, buffer).is_lt()) { cursor.prev(); } cursor .item() .filter(|hunk| target.cmp(&hunk.buffer_range.start, buffer).is_ge()) } /// Returns a patch mapping the provided main buffer snapshot to the base text of this diff. /// /// The returned patch is guaranteed to be accurate for all main buffer points in the provided range, /// but not necessarily for points outside that range. pub fn patch_for_buffer_range<'a>( &'a self, range: RangeInclusive<Point>, buffer: &'a text::BufferSnapshot, ) -> Patch<Point> { if !self.inner.base_text_exists { return Patch::new(vec![Edit { old: Point::zero()..buffer.max_point(), new: Point::zero()..Point::zero(), }]); } let mut edits_since_diff = Patch::new( buffer .edits_since::<Point>(&self.inner.buffer_snapshot.version) .collect::<Vec<_>>(), ); edits_since_diff.invert(); let mut start_point = edits_since_diff.old_to_new(*range.start()); if let Some(first_edit) = edits_since_diff.edits().first() { start_point = start_point.min(first_edit.new.start); } let original_snapshot = self.original_buffer_snapshot(); let base_text = self.base_text(); let mut cursor = self.inner.hunks.cursor(original_snapshot); self.hunk_before_buffer_anchor( original_snapshot.anchor_before(start_point), &mut cursor, original_snapshot, ); if cursor.item().is_none() { cursor.next(); } let mut prefix_edit = cursor.prev_item().map(|prev_hunk| Edit { old: Point::zero()..prev_hunk.buffer_range.end.to_point(original_snapshot), new: Point::zero()..prev_hunk.diff_base_byte_range.end.to_point(base_text), }); let mut range_end = edits_since_diff.old_to_new(*range.end()); if let Some(last_edit) = edits_since_diff.edits().last() { range_end = range_end.max(last_edit.new.end); } let range_end = original_snapshot.anchor_before(range_end); let hunk_iter = std::iter::from_fn(move || { if let Some(edit) = prefix_edit.take() { return Some(edit); } let hunk = cursor.item()?; if hunk .buffer_range .start .cmp(&range_end, original_snapshot) .is_gt() { return None; } let edit = Edit { old: hunk.buffer_range.to_point(original_snapshot), new: hunk.diff_base_byte_range.to_point(base_text), }; cursor.next(); Some(edit) }); edits_since_diff.compose(hunk_iter) } #[cfg(test)] pub(crate) fn patch_for_buffer_range_naive<'a>( &'a self, buffer: &'a text::BufferSnapshot, ) -> Patch<Point> { let original_snapshot = self.original_buffer_snapshot(); let edits_since: Vec<Edit<Point>> = buffer .edits_since::<Point>(original_snapshot.version()) .collect(); let mut inverted_edits_since = Patch::new(edits_since); inverted_edits_since.invert(); inverted_edits_since.compose( self.inner .hunks .iter() .map(|hunk| { let old_start = hunk.buffer_range.start.to_point(original_snapshot); let old_end = hunk.buffer_range.end.to_point(original_snapshot); let new_start = self .base_text() .offset_to_point(hunk.diff_base_byte_range.start); let new_end = self