3952 lines · 134.0 KB · Rust
RawDownload
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

Lines 1–500 of 3952.