2185 lines · 71.5 KB · Rust
RawDownload
use futures::channel::oneshot;use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, TaskLabel};use language::{Language, LanguageRegistry};use rope::Rope;use std::{    cmp::Ordering,    future::Future,    iter,    ops::Range,    sync::{Arc, LazyLock},};use sum_tree::SumTree;use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point, ToOffset as _};use util::ResultExt; pub static CALCULATE_DIFF_TASK: LazyLock<TaskLabel> = LazyLock::new(TaskLabel::new); pub struct BufferDiff {    pub buffer_id: BufferId,    inner: BufferDiffInner,    secondary_diff: Option<Entity<BufferDiff>>,} #[derive(Clone, Debug)]pub struct BufferDiffSnapshot {    inner: BufferDiffInner,    secondary_diff: Option<Box<BufferDiffSnapshot>>,} #[derive(Clone)]struct BufferDiffInner {    hunks: SumTree<InternalDiffHunk>,    pending_hunks: SumTree<PendingHunk>,    base_text: language::BufferSnapshot,    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)]pub enum DiffHunkSecondaryStatus {    HasSecondaryHunk,    OverlapsWithSecondaryHunk,    NoSecondaryHunk,    SecondaryHunkAdditionPending,    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,} /// 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>,} #[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, Default, Clone)]pub struct DiffHunkSummary {    buffer_range: Range<Anchor>,} impl sum_tree::Item for InternalDiffHunk {    type Summary = DiffHunkSummary;     fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {        DiffHunkSummary {            buffer_range: self.buffer_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(),        }    }} impl sum_tree::Summary for DiffHunkSummary {    type Context = text::BufferSnapshot;     fn zero(_cx: &Self::Context) -> Self {        Default::default()    }     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);    }} 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 {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        f.debug_struct("BufferDiffSnapshot")            .field("hunks", &self.hunks)            .finish()    }} impl BufferDiffSnapshot {    fn empty(buffer: &text::BufferSnapshot, cx: &mut App) -> BufferDiffSnapshot {        BufferDiffSnapshot {            inner: BufferDiffInner {                base_text: language::Buffer::build_empty_snapshot(cx),                hunks: SumTree::new(buffer),                pending_hunks: SumTree::new(buffer),                base_text_exists: false,            },            secondary_diff: None,        }    }     fn new_with_base_text(        buffer: text::BufferSnapshot,        base_text: Option<Arc<String>>,        language: Option<Arc<Language>>,        language_registry: Option<Arc<LanguageRegistry>>,        cx: &mut App,    ) -> impl Future<Output = Self> + use<> {        let base_text_pair;        let base_text_exists;        let base_text_snapshot;        if let Some(text) = &base_text {            let base_text_rope = Rope::from(text.as_str());            base_text_pair = Some((text.clone(), base_text_rope.clone()));            let snapshot = language::Buffer::build_snapshot(                base_text_rope,                language.clone(),                language_registry.clone(),                cx,            );            base_text_snapshot = cx.background_spawn(snapshot);            base_text_exists = true;        } else {            base_text_pair = None;            base_text_snapshot = Task::ready(language::Buffer::build_empty_snapshot(cx));            base_text_exists = false;        };         let hunks = cx            .background_executor()            .spawn_labeled(*CALCULATE_DIFF_TASK, {                let buffer = buffer.clone();                async move { compute_hunks(base_text_pair, buffer) }            });         async move {            let (base_text, hunks) = futures::join!(base_text_snapshot, hunks);            Self {                inner: BufferDiffInner {                    base_text,                    hunks,                    base_text_exists,                    pending_hunks: SumTree::new(&buffer),                },                secondary_diff: None,            }        }    }     pub fn new_with_base_buffer(        buffer: text::BufferSnapshot,        base_text: Option<Arc<String>>,        base_text_snapshot: language::BufferSnapshot,        cx: &App,    ) -> impl Future<Output = Self> + use<> {        let base_text_exists = base_text.is_some();        let base_text_pair = base_text.map(|text| (text, base_text_snapshot.as_rope().clone()));        cx.background_executor()            .spawn_labeled(*CALCULATE_DIFF_TASK, async move {                Self {                    inner: BufferDiffInner {                        base_text: base_text_snapshot,                        pending_hunks: SumTree::new(&buffer),                        hunks: compute_hunks(base_text_pair, buffer),                        base_text_exists,                    },                    secondary_diff: None,                }            })    }     #[cfg(test)]    fn new_sync(        buffer: text::BufferSnapshot,        diff_base: String,        cx: &mut gpui::TestAppContext,    ) -> BufferDiffSnapshot {        cx.executor().block(cx.update(|cx| {            Self::new_with_base_text(buffer, Some(Arc::new(diff_base)), None, None, cx)        }))    }     pub fn is_empty(&self) -> bool {        self.inner.hunks.is_empty()    }     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> {        self.inner.hunks_intersecting_range_rev(range, buffer)    }     pub fn base_text(&self) -> &language::BufferSnapshot {        &self.inner.base_text    }     pub fn base_texts_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_empty) = (left.remote_id(), left.is_empty());        let (new_id, new_empty) = (right.remote_id(), right.is_empty());        new_id == old_id || (new_empty && old_empty)    }} impl BufferDiffInner {    /// 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,    ) -> Option<Rope> {        let head_text = self            .base_text_exists            .then(|| self.base_text.as_rope().clone());        let index_text = unstaged_diff            .base_text_exists            .then(|| unstaged_diff.base_text.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..Anchor::MAX,                    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, buffer);            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(buffer);            }             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), buffer);         let mut unstaged_hunk_cursor = unstaged_diff.hunks.cursor::<DiffHunkSummary>(buffer);        unstaged_hunk_cursor.next(buffer);         // 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, buffer);             if let Some(unstaged_hunk) = skipped_unstaged.last() {                prev_unstaged_hunk_base_text_end = unstaged_hunk.diff_base_byte_range.end;                prev_unstaged_hunk_buffer_end = unstaged_hunk.buffer_range.end.to_offset(buffer);            }             // Find where this hunk is in the index if it doesn't overlap            let mut buffer_offset_range = buffer_range.to_offset(buffer);            let start_overshoot = buffer_offset_range.start - prev_unstaged_hunk_buffer_end;            let mut index_start = prev_unstaged_hunk_base_text_end + start_overshoot;             loop {                // Merge this hunk with any overlapping unstaged hunks.                if let Some(unstaged_hunk) = unstaged_hunk_cursor.item() {                    let unstaged_hunk_offset_range = unstaged_hunk.buffer_range.to_offset(buffer);                    if unstaged_hunk_offset_range.start <= buffer_offset_range.end {                        prev_unstaged_hunk_base_text_end = unstaged_hunk.diff_base_byte_range.end;                        prev_unstaged_hunk_buffer_end = unstaged_hunk_offset_range.end;                         index_start = index_start.min(unstaged_hunk.diff_base_byte_range.start);                        buffer_offset_range.start = buffer_offset_range                            .start                            .min(unstaged_hunk_offset_range.start);                        buffer_offset_range.end =                            buffer_offset_range.end.max(unstaged_hunk_offset_range.end);                         unstaged_hunk_cursor.next(buffer);                        continue;                    }                }                 // If any unstaged hunks were merged, then subsequent pending hunks may                // now overlap this hunk. Merge them.                if let Some(next_pending_hunk) = pending_hunks_iter.peek() {                    let next_pending_hunk_offset_range =                        next_pending_hunk.buffer_range.to_offset(buffer);                    if next_pending_hunk_offset_range.start <= buffer_offset_range.end {                        buffer_offset_range.end = next_pending_hunk_offset_range.end;                        pending_hunks_iter.next();                        continue;                    }                }                 break;            }             let end_overshoot = buffer_offset_range                .end                .saturating_sub(prev_unstaged_hunk_buffer_end);            let index_end = prev_unstaged_hunk_base_text_end + end_overshoot;            let index_byte_range = index_start..index_end;             let replacement_text = match new_status {                DiffHunkSecondaryStatus::SecondaryHunkRemovalPending => {                    log::debug!("staging hunk {:?}", buffer_offset_range);                    buffer                        .text_for_range(buffer_offset_range)                        .collect::<String>()                }                DiffHunkSecondaryStatus::SecondaryHunkAdditionPending => {                    log::debug!("unstaging hunk {:?}", buffer_offset_range);                    head_text                        .chunks_in_range(diff_base_byte_range.clone())                        .collect::<String>()                }                _ => {                    debug_assert!(false);                    continue;                }            };             edits.push((index_byte_range, replacement_text));        }        drop(pending_hunks_iter);        drop(old_pending_hunks);        self.pending_hunks = pending_hunks;         #[cfg(debug_assertions)] // invariants: non-overlapping and sorted        {            for window in edits.windows(2) {                let (range_a, range_b) = (&window[0].0, &window[1].0);                debug_assert!(range_a.end < range_b.start);            }        }         let mut new_index_text = Rope::new();        let mut index_cursor = index_text.cursor(0);         for (old_range, replacement_text) in edits {            new_index_text.append(index_cursor.slice(old_range.start));            index_cursor.seek_forward(old_range.end);            new_index_text.push(&replacement_text);        }        new_index_text.append(index_cursor.suffix());        Some(new_index_text)    }     fn hunks_intersecting_range<'a>(        &'a self,

Lines 1–500 of 2185.