use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task};use imara_diff::{Algorithm, Diff, InternedInput, sources::lines};use language::{ Capability, DiffOptions, Language, LanguageName, LanguageRegistry, language_settings::LanguageSettings, word_diff_ranges,};use rope::Rope;use std::{ cmp::Ordering, 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, debug_panic}; pub const MAX_WORD_DIFF_LINE_COUNT: usize = 5; pub struct BufferDiff { pub buffer_id: BufferId, base_text_buffer: Entity<language::Buffer>, diff_snapshot: Option<BufferDiffSnapshot>, secondary_diff: Option<Entity<BufferDiff>>, buffer_snapshot: text::BufferSnapshot,} #[derive(Clone)]pub struct BufferDiffSnapshot { hunks: SumTree<InternalDiffHunk>, pending_hunks: SumTree<PendingHunk>, base_text: language::BufferSnapshot, base_text_exists: bool, buffer_snapshot: text::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("hunks", &self.hunks) .field("remote_id", &self.base_text.remote_id()) .field("secondary_diff", &self.secondary_diff) .finish() }} #[derive(Clone)]pub struct BufferDiffUpdate { hunks: SumTree<InternalDiffHunk>, base_text: language::BufferSnapshot, base_text_exists: bool, buffer_snapshot: text::BufferSnapshot,} impl BufferDiffUpdate { pub fn set_base_text_snapshot( &mut self, base_text: language::BufferSnapshot, base_text_exists: bool, ) { self.base_text = base_text; self.base_text_exists = base_text_exists; }} #[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>, diff_base_point_range: Range<Point>, base_word_diffs: Vec<Range<usize>>, buffer_word_diffs: Vec<Range<Anchor>>,} #[derive(Debug, Clone, PartialEq, Eq)]pub struct PendingHunk { buffer_range: Range<Anchor>, diff_base_byte_range: Range<usize>, buffer_version: clock::Global, sense: PendingSense,} impl PendingHunk { pub fn new( buffer_range: Range<Anchor>, diff_base_byte_range: Range<usize>, buffer_version: clock::Global, sense: PendingSense, ) -> Self { Self { buffer_range, diff_base_byte_range, buffer_version, sense, } }} #[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum PendingSense { /// Override the secondary status of the matched hunk (used by the /// uncommitted diff to show a hunk as staging/unstaging in place). SetSecondaryStatus { stage: bool }, /// Suppress the matched hunk entirely (used by the unstaged/staged diffs so /// that a hunk disappears the moment it is staged/unstaged). Suppress,} #[derive(Debug, Clone)]pub struct DiffHunkSummary { buffer_range: Range<Anchor>, diff_base_byte_range: Range<usize>, added_rows: u32, removed_rows: u32,} impl sum_tree::Item for InternalDiffHunk { type Summary = DiffHunkSummary; fn summary(&self, buffer: &text::BufferSnapshot) -> Self::Summary { let buffer_start = self.buffer_range.start.to_point(buffer); let buffer_end = self.buffer_range.end.to_point(buffer); DiffHunkSummary { buffer_range: self.buffer_range.clone(), diff_base_byte_range: self.diff_base_byte_range.clone(), added_rows: buffer_end.row.saturating_sub(buffer_start.row), removed_rows: self .diff_base_point_range .end .row .saturating_sub(self.diff_base_point_range.start.row), } }} 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(), added_rows: 0, removed_rows: 0, } }} impl sum_tree::Summary for DiffHunkSummary { type Context<'a> = &'a text::BufferSnapshot; fn zero(buffer: &text::BufferSnapshot) -> Self { DiffHunkSummary { buffer_range: Anchor::min_min_range_for_buffer(buffer.remote_id()), diff_base_byte_range: 0..0, added_rows: 0, removed_rows: 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); self.added_rows += other.added_rows; self.removed_rows += other.removed_rows; }} 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 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 buffer_id(&self) -> BufferId { self.buffer_snapshot.remote_id() } pub fn buffer_snapshot(&self) -> &text::BufferSnapshot { &self.buffer_snapshot } pub fn is_empty(&self) -> bool { self.hunks.is_empty() } pub fn changed_row_counts(&self) -> (u32, u32) { let summary = self.hunks.summary(); (summary.added_rows, summary.removed_rows) } pub fn base_text_string(&self) -> Option<String> { self.base_text_exists.then(|| self.base_text.text()) } pub fn base_text_exists(&self) -> bool { self.base_text_exists } pub fn secondary_diff(&self) -> Option<&BufferDiffSnapshot> { self.secondary_diff.as_deref() } pub fn buffer_version(&self) -> &clock::Global { self.buffer_snapshot.version() } fn original_buffer_snapshot(&self) -> &text::BufferSnapshot { &self.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_deref(); let range = range.to_offset(buffer); let filter = move |summary: &DiffHunkSummary| { let summary_range = summary.buffer_range.to_offset(buffer); let before_start = summary_range.end < range.start; let after_end = summary_range.start > range.end; !before_start && !after_end }; self.hunks_intersecting_range_impl(filter, buffer, unstaged_counterpart) } /// Like [`hunks_intersecting_range`], but ignores optimistic pending hunks /// (both secondary-status overrides and suppressions) and does not compute a /// secondary status. pub fn raw_hunks_intersecting_range<'a>( &'a self, range: Range<Anchor>, buffer: &'a text::BufferSnapshot, ) -> impl 'a + Iterator<Item = DiffHunk> { let range = range.to_offset(buffer); let filter = move |summary: &DiffHunkSummary| { let summary_range = summary.buffer_range.to_offset(buffer); !(summary_range.end < range.start) && !(summary_range.start > range.end) }; self.hunks .filter::<_, DiffHunkSummary>(buffer, filter) .map(move |hunk| { let buffer_range = hunk.buffer_range.clone(); DiffHunk { range: buffer_range.to_point(buffer), diff_base_byte_range: hunk.diff_base_byte_range.clone(), buffer_range, secondary_status: DiffHunkSecondaryStatus::NoSecondaryHunk, base_word_diffs: hunk.base_word_diffs.clone(), buffer_word_diffs: hunk.buffer_word_diffs.clone(), } }) } /// Maps a range in this diff's main buffer to the range it covers in the /// base text, expanding to whole hunks wherever the range endpoints fall /// inside or touch a hunk (`edit_for_old_position` is inclusive on both /// boundaries, matching `raw_hunks_intersecting_range`). Used by the /// index-write path to compute the index-coordinate footprint of a staging /// operation; like the raw hunks, the mapping ignores optimistic pending /// hunks. pub fn base_text_range_for_buffer_range( &self, range: Range<Anchor>, buffer: &text::BufferSnapshot, ) -> Range<usize> { let point_range = range.to_point(buffer); let patch = self.patch_for_buffer_range(point_range.start..=point_range.end, buffer); let start_point = patch.edit_for_old_position(point_range.start).new.start; let end_point = patch.edit_for_old_position(point_range.end).new.end; let base_text = self.base_text(); let start = base_text.point_to_offset(start_point.min(base_text.max_point())); let end = base_text.point_to_offset(end_point.min(base_text.max_point())); start.min(end)..end } 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.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_deref(); 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.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.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.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.base_text_exists != other.base_text_exists { return false; } let left = &self.base_text; let right = &other.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(); }Lines 1–500 of 4326. Load more
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task}; use imara_diff::{Algorithm, Diff, InternedInput, sources::lines}; use language::{ Capability, DiffOptions, Language, LanguageName, LanguageRegistry, language_settings::LanguageSettings, word_diff_ranges, }; use rope::Rope; use std::{ cmp::Ordering, 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, debug_panic}; pub const MAX_WORD_DIFF_LINE_COUNT: usize = 5; pub struct BufferDiff { pub buffer_id: BufferId, base_text_buffer: Entity<language::Buffer>, diff_snapshot: Option<BufferDiffSnapshot>, secondary_diff: Option<Entity<BufferDiff>>, buffer_snapshot: text::BufferSnapshot, } #[derive(Clone)] pub struct BufferDiffSnapshot { hunks: SumTree<InternalDiffHunk>, pending_hunks: SumTree<PendingHunk>, base_text: language::BufferSnapshot, base_text_exists: bool, buffer_snapshot: text::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("hunks", &self.hunks) .field("remote_id", &self.base_text.remote_id()) .field("secondary_diff", &self.secondary_diff) .finish() } } #[derive(Clone)] pub struct BufferDiffUpdate { hunks: SumTree<InternalDiffHunk>, base_text: language::BufferSnapshot, base_text_exists: bool, buffer_snapshot: text::BufferSnapshot, } impl BufferDiffUpdate { pub fn set_base_text_snapshot( &mut self, base_text: language::BufferSnapshot, base_text_exists: bool, ) { self.base_text = base_text; self.base_text_exists = base_text_exists; } } #[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>, diff_base_point_range: Range<Point>, base_word_diffs: Vec<Range<usize>>, buffer_word_diffs: Vec<Range<Anchor>>, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct PendingHunk { buffer_range: Range<Anchor>, diff_base_byte_range: Range<usize>, buffer_version: clock::Global, sense: PendingSense, } impl PendingHunk { pub fn new( buffer_range: Range<Anchor>, diff_base_byte_range: Range<usize>, buffer_version: clock::Global, sense: PendingSense, ) -> Self { Self { buffer_range, diff_base_byte_range, buffer_version, sense, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PendingSense { /// Override the secondary status of the matched hunk (used by the /// uncommitted diff to show a hunk as staging/unstaging in place). SetSecondaryStatus { stage: bool }, /// Suppress the matched hunk entirely (used by the unstaged/staged diffs so /// that a hunk disappears the moment it is staged/unstaged). Suppress, } #[derive(Debug, Clone)] pub struct DiffHunkSummary { buffer_range: Range<Anchor>, diff_base_byte_range: Range<usize>, added_rows: u32, removed_rows: u32, } impl sum_tree::Item for InternalDiffHunk { type Summary = DiffHunkSummary; fn summary(&self, buffer: &text::BufferSnapshot) -> Self::Summary { let buffer_start = self.buffer_range.start.to_point(buffer); let buffer_end = self.buffer_range.end.to_point(buffer); DiffHunkSummary { buffer_range: self.buffer_range.clone(), diff_base_byte_range: self.diff_base_byte_range.clone(), added_rows: buffer_end.row.saturating_sub(buffer_start.row), removed_rows: self .diff_base_point_range .end .row .saturating_sub(self.diff_base_point_range.start.row), } } } 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(), added_rows: 0, removed_rows: 0, } } } impl sum_tree::Summary for DiffHunkSummary { type Context<'a> = &'a text::BufferSnapshot; fn zero(buffer: &text::BufferSnapshot) -> Self { DiffHunkSummary { buffer_range: Anchor::min_min_range_for_buffer(buffer.remote_id()), diff_base_byte_range: 0..0, added_rows: 0, removed_rows: 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); self.added_rows += other.added_rows; self.removed_rows += other.removed_rows; } } 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 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 buffer_id(&self) -> BufferId { self.buffer_snapshot.remote_id() } pub fn buffer_snapshot(&self) -> &text::BufferSnapshot { &self.buffer_snapshot } pub fn is_empty(&self) -> bool { self.hunks.is_empty() } pub fn changed_row_counts(&self) -> (u32, u32) { let summary = self.hunks.summary(); (summary.added_rows, summary.removed_rows) } pub fn base_text_string(&self) -> Option<String> { self.base_text_exists.then(|| self.base_text.text()) } pub fn base_text_exists(&self) -> bool { self.base_text_exists } pub fn secondary_diff(&self) -> Option<&BufferDiffSnapshot> { self.secondary_diff.as_deref() } pub fn buffer_version(&self) -> &clock::Global { self.buffer_snapshot.version() } fn original_buffer_snapshot(&self) -> &text::BufferSnapshot { &self.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_deref(); let range = range.to_offset(buffer); let filter = move |summary: &DiffHunkSummary| { let summary_range = summary.buffer_range.to_offset(buffer); let before_start = summary_range.end < range.start; let after_end = summary_range.start > range.end; !before_start && !after_end }; self.hunks_intersecting_range_impl(filter, buffer, unstaged_counterpart) } /// Like [`hunks_intersecting_range`], but ignores optimistic pending hunks /// (both secondary-status overrides and suppressions) and does not compute a /// secondary status. pub fn raw_hunks_intersecting_range<'a>( &'a self, range: Range<Anchor>, buffer: &'a text::BufferSnapshot, ) -> impl 'a + Iterator<Item = DiffHunk> { let range = range.to_offset(buffer); let filter = move |summary: &DiffHunkSummary| { let summary_range = summary.buffer_range.to_offset(buffer); !(summary_range.end < range.start) && !(summary_range.start > range.end) }; self.hunks .filter::<_, DiffHunkSummary>(buffer, filter) .map(move |hunk| { let buffer_range = hunk.buffer_range.clone(); DiffHunk { range: buffer_range.to_point(buffer), diff_base_byte_range: hunk.diff_base_byte_range.clone(), buffer_range, secondary_status: DiffHunkSecondaryStatus::NoSecondaryHunk, base_word_diffs: hunk.base_word_diffs.clone(), buffer_word_diffs: hunk.buffer_word_diffs.clone(), } }) } /// Maps a range in this diff's main buffer to the range it covers in the /// base text, expanding to whole hunks wherever the range endpoints fall /// inside or touch a hunk (`edit_for_old_position` is inclusive on both /// boundaries, matching `raw_hunks_intersecting_range`). Used by the /// index-write path to compute the index-coordinate footprint of a staging /// operation; like the raw hunks, the mapping ignores optimistic pending /// hunks. pub fn base_text_range_for_buffer_range( &self, range: Range<Anchor>, buffer: &text::BufferSnapshot, ) -> Range<usize> { let point_range = range.to_point(buffer); let patch = self.patch_for_buffer_range(point_range.start..=point_range.end, buffer); let start_point = patch.edit_for_old_position(point_range.start).new.start; let end_point = patch.edit_for_old_position(point_range.end).new.end; let base_text = self.base_text(); let start = base_text.point_to_offset(start_point.min(base_text.max_point())); let end = base_text.point_to_offset(end_point.min(base_text.max_point())); start.min(end)..end } 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.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_deref(); 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.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.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.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.base_text_exists != other.base_text_exists { return false; } let left = &self.base_text; let right = &other.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(); }