4326 lines · 149.3 KB · Rust
RawDownload
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.