1995 lines · 64.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};use language::{Language, LanguageRegistry};use rope::Rope;use std::cmp::Ordering;use std::mem;use std::{future::Future, iter, ops::Range, sync::Arc};use sum_tree::SumTree;use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point};use text::{AnchorRangeExt, ToOffset as _};use util::ResultExt; 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 {    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(        &mut self,        unstaged_diff: &Self,        stage: bool,        hunks: &[DiffHunk],        buffer: &text::BufferSnapshot,        file_exists: bool,    ) -> (Option<Rope>, SumTree<PendingHunk>) {        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 (rope, 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,                };                let tree = SumTree::from_item(hunk, buffer);                return (rope, tree);            }        };         let mut unstaged_hunk_cursor = unstaged_diff.hunks.cursor::<DiffHunkSummary>(buffer);        unstaged_hunk_cursor.next(buffer);         let mut pending_hunks = SumTree::new(buffer);        let mut old_pending_hunks = unstaged_diff            .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 old pending hunks            while old_pending_hunks                .item()                .is_some_and(|preceding_pending_hunk_item| {                    preceding_pending_hunk_item                        .buffer_range                        .overlaps(&buffer_range, buffer)                })            {                old_pending_hunks.next(buffer);            }             // merge into pending hunks            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 prev_unstaged_hunk_buffer_offset = 0;        let mut prev_unstaged_hunk_base_text_offset = 0;        let mut edits = Vec::<(Range<usize>, String)>::new();         // then, iterate over all pending hunks (both new ones and the existing ones) and compute the edits        for PendingHunk {            buffer_range,            diff_base_byte_range,            ..        } in pending_hunks.iter().cloned()        {            let skipped_hunks = unstaged_hunk_cursor.slice(&buffer_range.start, Bias::Left, buffer);             if let Some(secondary_hunk) = skipped_hunks.last() {                prev_unstaged_hunk_base_text_offset = secondary_hunk.diff_base_byte_range.end;                prev_unstaged_hunk_buffer_offset =                    secondary_hunk.buffer_range.end.to_offset(buffer);            }             let mut buffer_offset_range = buffer_range.to_offset(buffer);            let start_overshoot = buffer_offset_range.start - prev_unstaged_hunk_buffer_offset;            let mut index_start = prev_unstaged_hunk_base_text_offset + start_overshoot;             while let Some(unstaged_hunk) = unstaged_hunk_cursor.item().filter(|item| {                item.buffer_range                    .start                    .cmp(&buffer_range.end, buffer)                    .is_le()            }) {                let unstaged_hunk_offset_range = unstaged_hunk.buffer_range.to_offset(buffer);                prev_unstaged_hunk_base_text_offset = unstaged_hunk.diff_base_byte_range.end;                prev_unstaged_hunk_buffer_offset = 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);                 unstaged_hunk_cursor.next(buffer);            }             let end_overshoot = buffer_offset_range                .end                .saturating_sub(prev_unstaged_hunk_buffer_offset);            let index_end = prev_unstaged_hunk_base_text_offset + end_overshoot;             let index_range = index_start..index_end;            buffer_offset_range.end = buffer_offset_range                .end                .max(prev_unstaged_hunk_buffer_offset);             let replacement_text = if stage {                log::debug!("stage hunk {:?}", buffer_offset_range);                buffer                    .text_for_range(buffer_offset_range)                    .collect::<String>()            } else {                log::debug!("unstage hunk {:?}", buffer_offset_range);                head_text                    .chunks_in_range(diff_base_byte_range.clone())                    .collect::<String>()            };             edits.push((index_range, replacement_text));        }         debug_assert!(edits.iter().is_sorted_by_key(|(range, _)| range.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), pending_hunks)    }     fn hunks_intersecting_range<'a>(        &'a self,        range: Range<Anchor>,        buffer: &'a text::BufferSnapshot,        secondary: Option<&'a Self>,    ) -> impl 'a + Iterator<Item = DiffHunk> {        let range = range.to_offset(buffer);         let mut cursor = self            .hunks            .filter::<_, DiffHunkSummary>(buffer, move |summary| {                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            });         let anchor_iter = iter::from_fn(move || {            cursor.next(buffer);            cursor.item()        })        .flat_map(move |hunk| {            [                (                    &hunk.buffer_range.start,                    (hunk.buffer_range.start, hunk.diff_base_byte_range.start),                ),                (                    &hunk.buffer_range.end,                    (hunk.buffer_range.end, hunk.diff_base_byte_range.end),                ),            ]        });         let mut secondary_cursor = None;        let mut pending_hunks_cursor = None;        if let Some(secondary) = secondary.as_ref() {            let mut cursor = secondary.hunks.cursor::<DiffHunkSummary>(buffer);            cursor.next(buffer);            secondary_cursor = Some(cursor);            let mut cursor = secondary.pending_hunks.cursor::<DiffHunkSummary>(buffer);            cursor.next(buffer);            pending_hunks_cursor = Some(cursor);        }         let max_point = buffer.max_point();        let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter);        iter::from_fn(move || loop {            let (start_point, (start_anchor, start_base)) = summaries.next()?;            let (mut end_point, (mut end_anchor, end_base)) = summaries.next()?;             if !start_anchor.is_valid(buffer) {                continue;            }             if end_point.column > 0 && end_point < max_point {                end_point.row += 1;                end_point.column = 0;                end_anchor = buffer.anchor_before(end_point);            }             let mut secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;             let mut has_pending = false;            if let Some(pending_cursor) = pending_hunks_cursor.as_mut() {                if start_anchor                    .cmp(&pending_cursor.start().buffer_range.start, buffer)                    .is_gt()                {                    pending_cursor.seek_forward(&start_anchor, Bias::Left, buffer);                }                 if let Some(pending_hunk) = pending_cursor.item() {                    let mut pending_range = pending_hunk.buffer_range.to_point(buffer);                    if pending_range.end.column > 0 {                        pending_range.end.row += 1;                        pending_range.end.column = 0;                    }                     if pending_range == (start_point..end_point) {                        if !buffer.has_edits_since_in_range(                            &pending_hunk.buffer_version,                            start_anchor..end_anchor,                        ) {                            has_pending = true;                            secondary_status = pending_hunk.new_status;                        }                    }                }            }             if let (Some(secondary_cursor), false) = (secondary_cursor.as_mut(), has_pending) {                if start_anchor                    .cmp(&secondary_cursor.start().buffer_range.start, buffer)                    .is_gt()                {                    secondary_cursor.seek_forward(&start_anchor, Bias::Left, buffer);                }                 if let Some(secondary_hunk) = secondary_cursor.item() {                    let mut secondary_range = secondary_hunk.buffer_range.to_point(buffer);                    if secondary_range.end.column > 0 {                        secondary_range.end.row += 1;                        secondary_range.end.column = 0;                    }                    if secondary_range.is_empty() && secondary_hunk.diff_base_byte_range.is_empty()                    {                        // ignore                    } else if secondary_range == (start_point..end_point) {                        secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;                    } else if secondary_range.start <= end_point {                        secondary_status = DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk;                    }                }            }             return Some(DiffHunk {                range: start_point..end_point,                diff_base_byte_range: start_base..end_base,                buffer_range: start_anchor..end_anchor,                secondary_status,            });        })    }     fn hunks_intersecting_range_rev<'a>(        &'a self,

Lines 1–500 of 1995.