2673 lines · 89.7 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::{    BufferRow, Capability, DiffOptions, File, Language, LanguageName, LanguageRegistry,    language_settings::language_settings, word_diff_ranges,};use rope::Rope;use std::{cmp::Ordering, future::Future, iter, ops::Range, sync::Arc};use sum_tree::SumTree;use text::{Anchor, Bias, BufferId, OffsetRangeExt, 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>>,    // diff of the index vs head    secondary_diff: Option<Entity<BufferDiff>>,} #[derive(Clone)]pub struct BufferDiffSnapshot {    inner: BufferDiffInner<language::BufferSnapshot>,    secondary_diff: Option<Box<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 {    base_text_changed: bool,    inner: BufferDiffInner<Arc<str>>,} #[derive(Clone)]struct BufferDiffInner<BaseText> {    hunks: SumTree<InternalDiffHunk>,    pending_hunks: SumTree<PendingHunk>,    base_text: BaseText,    base_text_exists: bool,} #[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 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 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)    }     pub fn row_to_base_text_row(        &self,        row: BufferRow,        bias: Bias,        buffer: &text::BufferSnapshot,    ) -> u32 {        // TODO(split-diff) expose a parameter to reuse a cursor to avoid repeatedly seeking from the start        let target = buffer.anchor_before(Point::new(row, 0));        // Find the last hunk that starts before the target.        let mut cursor = self.inner.hunks.cursor::<DiffHunkSummary>(buffer);        cursor.seek(&target, Bias::Left);        if cursor            .item()            .is_none_or(|hunk| hunk.buffer_range.start.cmp(&target, buffer).is_gt())        {            cursor.prev();        }         let unclipped_point = if let Some(hunk) = cursor.item()            && hunk.buffer_range.start.cmp(&target, buffer).is_le()        {            // Found a hunk that starts before the target.            let hunk_base_text_end = cursor.end().diff_base_byte_range.end;            let unclipped_point = if target.cmp(&cursor.end().buffer_range.end, buffer).is_ge() {                // Target falls strictly between two hunks.                let mut unclipped_point = hunk_base_text_end.to_point(self.base_text());                unclipped_point +=                    Point::new(row, 0) - cursor.end().buffer_range.end.to_point(buffer);                unclipped_point            } else if bias == Bias::Right {                hunk_base_text_end.to_point(self.base_text())            } else {                hunk.diff_base_byte_range.start.to_point(self.base_text())            };            // Move the cursor so that at the next step we can clip with the start of the next hunk.            cursor.next();            unclipped_point        } else {            // Target is before the added region for the first hunk.            debug_assert!(self.inner.hunks.first().is_none_or(|first_hunk| {                target.cmp(&first_hunk.buffer_range.start, buffer).is_le()            }));            Point::new(row, 0)        };         // If the target falls in the region between two hunks, we added an overshoot above.        // There may be changes in the main buffer that are not reflected in the hunks,        // so we need to ensure this overshoot keeps us in the corresponding base text region.        let max_point = if let Some(next_hunk) = cursor.item() {            next_hunk                .diff_base_byte_range                .start                .to_point(self.base_text())        } else {            self.base_text().max_point()        };        unclipped_point.min(max_point).row    }} impl BufferDiffInner<Entity<language::Buffer>> {    /// Returns the new index text and new pending hunks.    fn stage_or_unstage_hunks_impl(        &mut self,        unstaged_diff: &Self,        stage: bool,        hunks: &[DiffHunk],        buffer: &text::BufferSnapshot,        file_exists: bool,        cx: &mut Context<BufferDiff>,    ) -> Option<Rope> {        let head_text = self            .base_text_exists            .then(|| self.base_text.read(cx).as_rope().clone());        let index_text = unstaged_diff            .base_text_exists            .then(|| unstaged_diff.base_text.read(cx).as_rope().clone());         // If the file doesn't exist in either HEAD or the index, then the        // entire file must be either created or deleted in the index.        let (index_text, head_text) = match (index_text, head_text) {            (Some(index_text), Some(head_text)) if file_exists || !stage => (index_text, head_text),            (index_text, head_text) => {                let (new_index_text, new_status) = if stage {                    log::debug!("stage all");                    (                        file_exists.then(|| buffer.as_rope().clone()),                        DiffHunkSecondaryStatus::SecondaryHunkRemovalPending,                    )                } else {                    log::debug!("unstage all");                    (                        head_text,                        DiffHunkSecondaryStatus::SecondaryHunkAdditionPending,                    )                };                 let hunk = PendingHunk {                    buffer_range: Anchor::min_max_range_for_buffer(buffer.remote_id()),                    diff_base_byte_range: 0..index_text.map_or(0, |rope| rope.len()),                    buffer_version: buffer.version().clone(),                    new_status,                };                self.pending_hunks = SumTree::from_item(hunk, buffer);                return new_index_text;            }        };         let mut pending_hunks = SumTree::new(buffer);        let mut old_pending_hunks = self.pending_hunks.cursor::<DiffHunkSummary>(buffer);         // first, merge new hunks into pending_hunks        for DiffHunk {            buffer_range,            diff_base_byte_range,            secondary_status,            ..        } in hunks.iter().cloned()        {            let preceding_pending_hunks = old_pending_hunks.slice(&buffer_range.start, Bias::Left);            pending_hunks.append(preceding_pending_hunks, buffer);             // Skip all overlapping or adjacent old pending hunks            while old_pending_hunks.item().is_some_and(|old_hunk| {                old_hunk                    .buffer_range                    .start                    .cmp(&buffer_range.end, buffer)                    .is_le()            }) {                old_pending_hunks.next();            }             if (stage && secondary_status == DiffHunkSecondaryStatus::NoSecondaryHunk)                || (!stage && secondary_status == DiffHunkSecondaryStatus::HasSecondaryHunk)            {                continue;            }             pending_hunks.push(                PendingHunk {                    buffer_range,                    diff_base_byte_range,                    buffer_version: buffer.version().clone(),                    new_status: if stage {                        DiffHunkSecondaryStatus::SecondaryHunkRemovalPending                    } else {                        DiffHunkSecondaryStatus::SecondaryHunkAdditionPending                    },                },                buffer,            );        }        // append the remainder        pending_hunks.append(old_pending_hunks.suffix(), buffer);         let mut unstaged_hunk_cursor = unstaged_diff.hunks.cursor::<DiffHunkSummary>(buffer);        unstaged_hunk_cursor.next();         // then, iterate over all pending hunks (both new ones and the existing ones) and compute the edits        let mut prev_unstaged_hunk_buffer_end = 0;        let mut prev_unstaged_hunk_base_text_end = 0;        let mut edits = Vec::<(Range<usize>, String)>::new();        let mut pending_hunks_iter = pending_hunks.iter().cloned().peekable();        while let Some(PendingHunk {            buffer_range,            diff_base_byte_range,            new_status,            ..        }) = pending_hunks_iter.next()        {            // Advance unstaged_hunk_cursor to skip unstaged hunks before current hunk            let skipped_unstaged = unstaged_hunk_cursor.slice(&buffer_range.start, Bias::Left);

Lines 1–500 of 2673.